import tensorflow as tf
import threading

from tf_agents.metrics import tf_py_metric
from tf_agents.metrics import tf_metrics
from tf_agents.metrics import tf_metric
from alf.metrics import py_metrics

class TFPyMetric(tf_metric.TFStepMetric):
    """
    The difference from tf_metrics.TFPyMetric is that we allow using
    lock here. Other code is just copied.
    """

    def __init__(self, py_metric, name=None, dtype=tf.float32):
        """Creates a TF metric given a py metric to wrap.

        py_metric (py_metrics.StreamingMetric): A batched python metric to wrap.
        name (str): Name of the metric.
        dtype (tf.dtype): Data type of the metric.
        """
        name = name or py_metric.name
        super(TFPyMetric, self).__init__(name=name)
        self._py_metric = py_metric
        self._dtype = dtype
        self._lock = threading.Lock()

    def call(self, trajectory, id):
        """Update the value of the metric using trajectory.

        The trajectory can be either batched or un-batched depending on
        the expected inputs for the py_metric being wrapped.

        Args:
            trajectory (Trajectory): tf_agents trajectory data
            id (tf.int32): indicates which environment generated `trajectory`

        Output:
            trajectory (Trajectory) :
                the argument itself, for easy chaining.
            id (tf.int32):
        """

        def _call(id, *flattened_trajectories):
            with self._lock:
                flat_sequence = [x.numpy() for x in flattened_trajectories]
                packed_trajectories = tf.nest.pack_sequence_as(
                    structure=(trajectory), flat_sequence=flat_sequence)
                return self._py_metric(packed_trajectories, id.numpy())

        flattened_trajectories = tf.nest.flatten(trajectory)
        metric_op = tf.py_function(
            _call, [id] + flattened_trajectories, [],
            name='metric_call_py_func')

        with tf.control_dependencies([metric_op]):
            return tf.nest.map_structure(tf.identity, trajectory), id

    def result(self):
        def _result():
            with self._lock:
                return self._py_metric.result()

        result_value = tf.py_function(
            _result, [], self._dtype, name='metric_result_py_func')
        if not tf.executing_eagerly():
            result_value.set_shape(())
        return result_value

    def reset(self):
        def _reset():
            with self._lock:
                return self._py_metric.reset()

        return tf.py_function(_reset, [], [], name='metric_reset_py_func')

class AverageReturnMetric(TFPyMetric):
    """
    Metric to compute the average return.
    Use our customized py_metrics.AverageReturnMetric
    """

    def __init__(self,
                 num_envs,
                 env_batch_size,
                 buffer_size=None,
                 name='AverageReturn',
                 dtype=tf.float32):
        py_metric = py_metrics.AverageReturnMetric(num_envs, env_batch_size,
                                                   buffer_size, name)
        super(AverageReturnMetric, self).__init__(
            py_metric=py_metric, name=name, dtype=dtype)

class AverageEpisodeLengthMetric(TFPyMetric):
    """
    Metric to compute the average episode length.
    Use our customized py_metrics.AverageEpisodeLengthMetric.
    """

    def __init__(self,
                 num_envs,
                 env_batch_size,
                 buffer_size=None,
                 name='AverageEpisodeLength',
                 dtype=tf.float32):

        py_metric = py_metrics.AverageEpisodeLengthMetric(
            num_envs, env_batch_size, buffer_size, name)
        super(AverageEpisodeLengthMetric, self).__init__(
            py_metric=py_metric, name=name, dtype=dtype)

class NumberOfEpisodes(tf_metrics.NumberOfEpisodes):
    """
    Overload call() to accept the other arg (env id) and ignore it.
    """

    def __init__(self, name='NumberOfEpisodes', dtype=tf.int64):
        super(NumberOfEpisodes, self).__init__(name=name, dtype=dtype)

    def call(self, trajectory, id):
        """This metric doesn't care about which env the traj comes from"""
        return super().call(trajectory)

class EnvironmentSteps(tf_metrics.EnvironmentSteps):
    """
    Overload call() to accept the other arg (env id) and ignore it.
    """

    def __init__(self, name='EnvironmentSteps', dtype=tf.int64):
        super(EnvironmentSteps, self).__init__(name=name, dtype=dtype)

    def call(self, trajectory, id):
        """This metric doesn't care about which env the traj comes from"""
        return super().call(trajectory)


'''
Given a matrix of 1s and 0s, return the number of "islands" in the matrix. A 1 represents land and 0 represents water, so an island is a group of 1s that are neighboring whose perimeter is surrounded by water.

For example, this matrix has 4 islands.

1 0 0 0 0
0 0 1 1 0
0 1 1 0 0
0 0 0 0 0
1 1 0 0 1
1 1 0 0 1
'''

from typing import List, Tuple

def countIslands(matrix: List[List[int]]):
    # Recursive function to build out an island. Will mark an entire island as traversed and return 1 at the top level.
    def rCountIslands(matrix: List[List[int]], coords: Tuple[int, int], traversed: List[List[bool]]):
        y, x = coords

        traversed[y][x] = True

        # West
        if x > 0 and matrix[y][x-1] == 1 and not traversed[y][x-1]:
            rCountIslands(matrix, (y, x-1), traversed)
        # Southwest
        if x > 0 and y < len(matrix)-1 and matrix[y+1][x-1] == 1 and not traversed[y+1][x-1]:
            rCountIslands(matrix, (y+1, x-1), traversed)
        # South
        if y < len(matrix)-1 and matrix[y+1][x] == 1 and not traversed[y+1][x]:
            rCountIslands(matrix, (y+1, x), traversed)
        # Southeast
        if x < len(matrix[0])-1 and y < len(matrix)-1 and matrix[y+1][x+1] == 1 and not traversed[y+1][x+1]:
            rCountIslands(matrix, (y+1, x+1), traversed)
        # East
        if x < len(matrix[0])-1 and matrix[y][x+1] == 1 and not traversed[y][x+1]:
            rCountIslands(matrix, (y, x+1), traversed)
        # Northeast
        if x < len(matrix[0])-1 and y > 0 and matrix[y-1][x+1] == 1 and not traversed[y-1][x+1]:
            rCountIslands(matrix, (y-1, x+1), traversed)
        # North
        if y > 0 and matrix[y-1][x] == 1 and not traversed[y-1][x]:
            rCountIslands(matrix, (y-1, x), traversed)
        # Northwest
        if x > 0 and y > 0 and matrix[y-1][x-1] == 1 and not traversed[y-1][x-1]:
            rCountIslands(matrix, (y-1, x-1), traversed)

        return 1

    # Setup all False traversed matrix and a count of 0.
    traversed = [[False for x in range(0, len(matrix[0]))] for y in range(0, len(matrix))]
    count = 0
    # Traverse through the matrix.
    for y in range(0, len(matrix)):
        for x in range(0, len(matrix[0])):
            if not traversed[y][x]:
                if matrix[y][x] == 1:
                    count += rCountIslands(matrix, (y, x), traversed)
                traversed[y][x] = True

    return count

island_map = [
    [1, 0, 0, 0, 0],
    [0, 0, 1, 1, 0],
    [0, 1, 1, 0, 0],
    [0, 0, 0, 0, 0],
    [1, 1, 0, 0, 1],
    [1, 1, 0, 0, 1],
]

print(countIslands(island_map))

island_map_2 = [
    [0, 0, 0, 0, 1],
    [1, 1, 1, 0, 1],
    [1, 0, 1, 0, 1],
    [1, 0, 0, 0, 1],
    [1, 1, 1, 1, 1],
]

print(countIslands(island_map_2))


print("Importing libraries")
from keras import backend as K
import tensorflow as tf
from keras.utils.vis_utils import plot_model
import pandas as pd
from sklearn.metrics import mean_squared_error
from datetime import datetime as dt
import numpy as np
import keras
from tensorflow.python.ops import math_ops
from tensorflow.keras.layers import BatchNormalization
import scipy
from sklearn.decomposition import TruncatedSVD

error_1_weight=2
error_2_weight=1

def data_preprocessing(df,numeric_col_list,n_components,target):
    one_hot = pd.get_dummies(df.drop([numeric_col_list],axis = 1))
    df2 = pd.concat([df,one_hot],axis=1)
    csr_sparse_matrix = scipy.sparse.csr_matrix(df2.values)
    pca = TruncatedSVD(n_components=n_components)
    principalComponents = pca.fit_transform(csr_sparse_matrix)
    print(pca.explained_variance_ratio_.sum())
    principalDf = pd.DataFrame(data = principalComponents)
    df2 = pd.concat([principalDf, df2[[target]]], axis = 1)
    return df2

def customLossCreator(error_1_weight, error_2_weight):
    def custom_loss(y_true, y_pred):
        diff =  math_ops.sub(y_true , y_pred)
        diff_error_1 = tf.math.multiply(error_1_weight,tf.math.multiply(diff,diff))
        diff_error_2 = tf.math.multiply(error_2_weight,tf.math.multiply(diff,diff))
        diff4 = tf.where(tf.greater(diff, 0), diff_error_1, diff_error_2)
        loss = K.mean(diff4, axis=-1) #mean over last dimension
        return loss
    return custom_loss

def createModel(X_train):
    return keras.Sequential([
                   keras.layers.Dense(32, activation='relu', input_shape=(1,X_train.shape[1]), dtype='float32'),
                   BatchNormalization(),
                   keras.layers.Dense(16, activation='relu'),
                   BatchNormalization(),
                   keras.layers.Dense(8, activation='relu'),
                   BatchNormalization(),
                   keras.layers.Dense(1)])

def fetchNumbers(model, X_train,y_train,test_main_X,test_main_y,model_name):
    y_pred = model.predict(X_train)
    print('MSE:',mean_squared_error(y_train, y_pred))
    y_pred_round = [np.round(x) for x in y_pred]
    print('Rounded MSE:',mean_squared_error(y_train, y_pred_round))
    y_train_arr = np.array(y_train)
    accuracy = [1  for i in range(0,len(y_train)) if (y_train_arr[i] == y_pred_round[i])]
    error_1 = [1  for i in range(0,len(y_train)) if y_train_arr[i] > y_pred_round[i]]
    error_2 = [1  for i in range(0,len(y_train)) if y_train_arr[i] < y_pred_round[i] ]
    print('Train BAE Nos:',np.sum(accuracy)/len(y_train),np.sum(error_1)/len(y_train),np.sum(error_2)/len(y_train))

    y_pred_test = model.predict(test_main_X)
    print('MSE:',mean_squared_error(test_main_y, y_pred_test))
    y_pred_round_test = [np.round(x) for x in y_pred_test]
    print('Rounded MSE:',mean_squared_error(test_main_y, y_pred_round_test))
    y_test_arr = np.array(test_main_y)
    accuracy = [1  for i in range(0,len(test_main_y)) if (y_test_arr[i] == y_pred_round_test[i])]
    error_1 = [1  for i in range(0,len(test_main_y)) if y_test_arr[i] > y_pred_round_test[i]]
    error_2 = [1  for i in range(0,len(test_main_y)) if y_test_arr[i] < y_pred_round_test[i]]
    print('Test BAE Nos:',np.sum(accuracy)/len(test_main_y),np.sum(error_1)/len(test_main_y),np.sum(error_2)/len(test_main_y))

    model.save( 'reg_model_' + str(dt.today().date()) + model_name + '.h5')
    X_train['actual'] = y_train
    X_train['predicted'] = y_pred_round
    return X_train

def createAndSaveModel(main_X, main_y,test_main_X,test_main_y,model_name, breaches_weight, earliness_weight):

    tf.config.run_functions_eagerly(True)
    X_train = main_X.copy()
    y_train = main_y.copy()
    model = createModel(X_train)
    model.compile(loss=customLossCreator(error_1_weight,error_2_weight), optimizer='sgd')
    plot_model(model, to_file=model_name + '.png', show_shapes=True, show_layer_names=True)
    model.fit(X_train, y_train, batch_size=256, epochs=8)
    return fetchNumbers(model, X_train, y_train, test_main_X,test_main_y,model_name)

def data_split(df,time_interval,target):
    train_data = df[:time_interval].sample(frac=1)
    test_data=df[time_interval:]
    main_X=train_data.drop([target],axis = 1)
    main_y = train_data[target].astype('float')
    test_main_X=test_data.drop([target],axis = 1)
    test_main_y=test_data[target].astype('float')
    return main_X, main_y, test_main_X, test_main_y

choice_set=[(error_1_weight,error_2_weight)]

print('read your dataframe')
print('define numerical col, target and number of principle components')

'''
1.pass the dataframe through :
   a.data_preprocessing
   b.data_split

2.pass the main_X, main_y, test_main_X, test_main_y found in above through createAndSaveModel function

3.Run the below line of codes:
    result_set=[createAndSaveModel(main_X, main_y,test_main_X,test_main_y, 'Model-v0.1'+str(i)+str(j), i,j) for i,j in choice_set]
    res = result_set[0]
    res['predicted'] = res['predicted'].map(lambda x: x[0])
    df.join(res[['actual','predicted']]).to_csv('Results_'+ str(dt.today().date()) + '.csv.gz', compression = 'gzip')

'''
##result_set=[createAndSaveModel(main_X, main_y,test_main_X,test_main_y, 'Model-v0.1'+str(i)+str(j), i,j) for i,j in choice_set]


""" This module includes the unittest class for the fcn8 model and a decorator
class for prittfy the console output."""

from __future__ import absolute_import
import unittest
import warnings

import torch
# from torch.nn import parameter
# from torch.autograd.gradcheck import gradcheck
# from torch.autograd import Variable
# import torch.nn as nn
from SemanticSegmentation.models.fcn8 import FCN8

class LineInfo():
    """ Deco Class for Lines """
    def __init__(self, lineformat:str = '-', linelength:int = 80,
                 info:tuple = ('', '')) -> None:
        self.lineformat = lineformat
        self.linelength = linelength
        self.info = info

    def __call__(self, func, *args, **kwargs):
        """ Prints Lines above and after the decorated func """
        def inner(*args, **kwargs):
            print(self.linelength * self.lineformat)
            print(self.info[0])
            func(*args, **kwargs)
            print(self.info[1])
            print(self.linelength * self.lineformat)
        return inner

    def __repr__(self) -> str:
        return 'LineInfo(lineformat=%r, linelength=%r, info=%r)' \
            % (self.lineformat, self.linelength, self.info)

class TestFCN8(unittest.TestCase):
    """ Unittest class for the fcn8 modell """
    def setUp(self):
        self.model = FCN8(8, pretrained=True)
        torch.manual_seed(0)
        # instead of zero init for score tensors use random init
        self.x = torch.rand((4, 3, 45, 45))

    @LineInfo(linelength=70, info=('Test Forward startet','Test Forward ended'))
    def testForward(self):
        """ Tests the FCN8.forward(x) function
        what is tested:
            - number of elements in output
            - shape of output
            - value at a specific index
            - max value if score is init zero_
        """

        self.changeWeights('random')
        output = self.model.forward(self.x)

        self.assertEqual(output.shape.numel(), 64800)
        self.assertEqual(list(output.shape), [4, 8, 45, 45])
        self.assertEqual(float(output[3][4][44][4]), 2362961408.0)

        self.changeWeights('zero')
        output = self.model.forward(self.x)

        self.assertEqual(torch.max(output), 0.0)

    def changeWeights(self, value:str = 'zero'):
        """ function to change the init value of score layers """
        if value == 'zero':
            self.model.score_fr[6].weight.data.zero_()
            self.model.score_fr[6].bias.data.zero_()
            self.model.score_pool3.weight.data.zero_()
            self.model.score_pool3.bias.data.zero_()
            self.model.score_pool4.weight.data.zero_()
            self.model.score_pool4.bias.data.zero_()
        elif value == 'random':
            self.model.score_fr[6].weight.data.random_()
            self.model.score_fr[6].bias.data.random_()
            self.model.score_pool3.weight.data.random_()
            self.model.score_pool3.bias.data.random_()
            self.model.score_pool4.weight.data.random_()
            self.model.score_pool4.bias.data.random_()
        else:
            warnings.warn(
                "weights stays unchanged use value == 'random' or 'zero'")

    '''def atest_sanity(self):
        input = (Variable(torch.randn(20, 20).double(), requires_grad=True), )
        model = nn.Linear(20, 1).double()
        test = gradcheck(model, input, eps=1e-6, atol=1e-4)
        print(test)
    '''

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


from question_bank import Question
from data import question_data
from quiz_brain import QuizBrain

question_bank=[]
for question in question_data:
    text=question["text"]
    answer=question["answer"]
    current_question=Question(text,answer)
    question_bank.append(current_question)
quiz=QuizBrain(question_bank)
while quiz.any_more_ques():
    quiz.next_question()
print("the game is over")
print(f"your final score is {quiz.score}/{quiz.question_number}")


from datetime import datetime
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import generics
from .models import TrackInformation
from .models import ArtistInformation
from symphony.serializer import TrackInformationSerializer
from symphony.serializer import ArtistInformationSerializer

class ArtistTrackInfoDateFilter(generics.ListAPIView):
    """This class handels the GET request for date and artist filter"""
    # TODO: Try to integrate with TrackInformationViews
    serializer_class = TrackInformationSerializer

    def get_queryset(self):
        """This method is called with the URL for tracks is passed with ArtistId and Release date."""
        artistId = self.kwargs.get('artistId')
        releaseDate = self.kwargs.get('releaseDate')
        queryset = TrackInformation.objects.all()
        da = datetime.strptime(releaseDate, '%d-%m-%Y')
        filter_query = queryset.filter(artistId__artistId=artistId, releaseDate__lte=da)
        return filter_query

class ArtistInformationQuerySet(generics.ListAPIView):
    """This class is specifically designed to handel GET request for artist ID. For E.G if there is Request for artistID
    then this view class will be called."""
    serializer_class = TrackInformationSerializer

    def get_queryset(request):
        artist_id = request.kwargs.get('artistId')
        if not artist_id:
            return Response({"response": 'FAIL', 'message': 'Please specify artistID'})
        try:
            artist_obj = ArtistInformation.objects.get(artistId=artist_id)
        except ArtistInformation.DoesNotExist:
            return []
        track_queryset = TrackInformation.objects.filter(artistId=artist_obj)
        return track_queryset

class ArtistInformationView(APIView):
    """ This view is for handling operation for Artist Information. It will perform GET, POST, DELETE request
    for artist information."""

    def get(self, pk=None):
        """Performs GET operation. Returns a Response with all artist information"""
        """This will handel all the GET request."""
        track_info = ArtistInformation.objects.all()
        serializer = ArtistInformationSerializer(track_info, many=True)
        return Response({"artist_info": serializer.data})

    def post(self, request):
        """Performs POST operation. Creates new artist record.  Returns a Response with artist information"""

        artist_name = request.data.get('name')
        serializer = ArtistInformationSerializer(data={'artistName': artist_name})
        if serializer.is_valid(raise_exception=True):
            artist_save = serializer.save()
        return Response({"response": 'SUCCESS', 'artistId': '{}'.format(artist_save.artistId),
                         "message": "Artist'{}' created successfully".format(artist_save.artistName)})

    def delete(self, request):
        """Performs DELETE operation. Deleted the artisrt ID record from the DB.
        Returns a Response with all artist information"""
        artistId = request.data.get('artistId')
        try:
            del_obj = ArtistInformation.objects.get(artistId=artistId)
            del_obj.delete()
            return Response({'response': 'SUCCESS', 'message': "Artist '{}' Deleted Successfully".format(artistId)})
        except ArtistInformation.DoesNotExist:
            return Response({'response': 'FAIL'})

class TrackInformationView(APIView):
    """This class handels the GET, POST, PUT operations for Tracks"""

    def check_track_exsist(self, **kwargs):
        """This is a custom method for retriving trackId and artistId"""
        try:
            artist_obj = TrackInformation.objects.get(trackId=kwargs.get('trackId')[0], artistId__artistId=kwargs.get('artistId')[0])
        except TrackInformation.DoesNotExist:
            artist_obj = []
        return artist_obj

    def get(self, request):
        track_info = TrackInformation.objects.all()
        serializer = TrackInformationSerializer(track_info, many=True)
        return Response({"track_info": serializer.data})

    def post(self, request):
        track_info = request.data.copy()
        track_info['releaseDate'] = datetime.datetime.now()
        serializer = TrackInformationSerializer(data=track_info, many=True)
        if serializer.is_valid(raise_exception=True):
            track_save = serializer.save()
        return Response({"response": 'SUCCESS', 'trackId': '{}'.format(track_save.artistId),
                         "message": "Track'{}' created successfully".format(track_save.trackName)})

    def put(self, request):
        """This method handels the Updates on Track Information"""
        da = request.data.copy()
        obj_data = self.check_track_exsist(**da)
        print(da)
        if not self.check_track_exsist(**da):
            return Response({'response': 'FAIL', 'message': 'Please check TrackID/ArtistID'})
        serializer = TrackInformationSerializer(obj_data, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)


import csv
import traceback
from datetime import datetime, timezone

from django.db import DatabaseError, connection
from django.utils.timezone import is_aware, make_aware
from git.exc import GitCommandError
from github import Repository
from gitlab.v4.objects import projects
from pydriller import Repository as PyDrillerRepository
from pydriller.domain.commit import Commit as PyDrillerCommit

from .models import Author, Commit, CommittedFile, MergeRequest, ensure_repository
from .sql import QUERY_SQL, STATS_SQL
from .utils import (
    display_url,
    gitlab_ts_to_datetime,
    log,
    normalize_branches,
    redact_http_url,
    should_exclude_from_stats,
)

GITLAB_TIMETSAMP_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"

#
# notes about timezone handling
#
#  1. USE_TZ is set to True, which means DateTime field in models are required to have a tzinfo component
#  2. For uniformity, we use timezone.utc for all datetime objects, which means some conversion is needed here
#  3. Github APIs returns native datetime object and the timezone is assumed to be in UTC
#  4. Gitlab APIs returns timetime as stringm with "Z" at the end, also in UTC
#  5. In model Repository, last_commit_at value is from Commit object, which is a tz aware datetime object
#     (need to validate accuracy here?). The last_indexed_at created in this module, UTC is used.
#

def index_commits(
    clone_url: str, git_repo_type: str = "", show_progress: bool = False, index_all: bool = False, timeout: int = 28800
) -> int:
    n_branch_updates, n_new_commits = 0, 0
    log_url = display_url(redact_http_url(clone_url))

    try:
        repo = ensure_repository(clone_url, git_repo_type)
        if repo is None:
            log(f"### cannot create repostitory object for {log_url}")
            return 0

        if repo.is_active is False:
            log(f"### skipping inactive repository {log_url}")
            return 0

        log(f"starting to index {log_url}")
        start_t = datetime.now()

        # use list comprehension to force loading of commits
        old_commits = {}
        for commit in repo.commits.all():
            old_commits[commit.sha] = commit

        if repo.last_commit_at and not index_all:
            index_since = repo.last_commit_at
        else:
            index_since = datetime.min

        for git_commit in PyDrillerRepository(
            clone_url,
            include_refs=True,
            include_remotes=True,
            since=index_since,
        ).traverse_commits():
            # impose some timeout to avoid spending tons of time on very large repositories
            if (datetime.now() - start_t).seconds > timeout:  # pragma: no cover
                print(f"### indexing not done after {timeout} seconds, aborting {log_url}")
                break

            if git_commit.hash in old_commits:
                # we've seen this commit before, just compare branches and update
                # if needed
                commit = old_commits[git_commit.hash]
                new_branches = normalize_branches(git_commit.branches)
                if new_branches != commit.branches:
                    commit.branches = new_branches
                    commit.save()
                    n_branch_updates += 1
            else:
                # check if the same repo is already linked to another repo
                try:
                    commit = Commit.objects.get(sha=git_commit.hash)
                except Commit.DoesNotExist:
                    commit = _new_commit_(git_commit)
                repo.commits.add(commit)

                if repo.last_commit_at is None or (commit.created_at and commit.created_at > repo.last_commit_at):
                    repo.last_commit_at = commit.created_at

                n_new_commits += 1

            nn = n_new_commits + n_branch_updates
            if nn > 0 and nn % 200 == 0 and show_progress:
                log(f"indexed {n_new_commits:5,} new commits and {n_branch_updates:5,} branch updates")

        if (n_new_commits + n_branch_updates) > 0:
            log(f"indexed {n_new_commits:5,} new commits and {n_branch_updates:5,} branch updates in the repository")

        repo.last_indexed_at = datetime.utcnow().replace(tzinfo=timezone.utc)
        repo.save()

        return n_new_commits + n_branch_updates

    except GitCommandError as e:
        print(f"{e._cmdline} returned {e.stderr} for {log_url}")
    except DatabaseError as e:
        exc = traceback.format_exc()
        print(f"DatabaseError indexing repository {log_url} => {str(e)}\n{exc}")
    except Exception as e:  # pragma: no cover
        exc = traceback.format_exc()
        print(f"Exception indexing repository {log_url} => {str(e)}\n{exc}")

    return 0

def index_gitlab_merge_requests(project: projects.Project, show_progress: bool = False) -> int:
    n_requests = 0
    log_url = display_url(project.http_url_to_repo)

    try:
        repo = ensure_repository(project.http_url_to_repo, "gitlab")
        if repo is None:
            log(f"### cannot create repostitory object for {log_url}")
            return 0

        if repo.is_active is False:
            log(f"### skipping inactive repository {log_url}")
            return 0

        log(f"starting to index merge requests for {log_url}")

        merge_requests = project.mergerequests.list(all=True)
        for mr in merge_requests:
            mr_id = mr.get_id()

            if mr.state not in ["closed", "merged"]:
                # index only closed or merged merge requests
                continue

            db_obj = MergeRequest.objects.filter(request_id=mr_id, repo=repo).first()
            if db_obj is not None:
                # merge request already indexed
                continue

            is_merged, merged_by_username, merge_commit_sha = False, None, None
            if mr.state == "merged":
                is_merged, merged_by_username = True, mr.merge_user["username"]
                merge_commit_sha = mr.squash_commit_sha if mr.squash else mr.merge_commit_sha

            MergeRequest.objects.create(
                repo=repo,
                request_id=mr_id,
                state=mr.state,
                source_branch=mr.source_branch,
                target_branch=mr.target_branch,
                source_sha=mr.sha,
                merge_sha=merge_commit_sha,
                created_at=gitlab_ts_to_datetime(mr.created_at),
                merged_at=gitlab_ts_to_datetime(mr.merged_at),
                updated_at=gitlab_ts_to_datetime(mr.updated_at),
                # first_comment_at = models.CharField(max_length=32)
                is_merged=is_merged,
                merged_by_username=merged_by_username,
            ).save()

            n_requests += 1

            if n_requests > 0 and n_requests % 50 == 0 and show_progress:
                log(f"indexed {n_requests:3,} merge requests ")

        if n_requests > 0:
            log(f"indexed {n_requests:3,} merge requests in the repository")

        return n_requests

    except GitCommandError as e:
        print(f"{e._cmdline} returned {e.stderr} for {log_url}")
    except DatabaseError as e:
        exc = traceback.format_exc()
        print(f"DatabaseError indexing repository {log_url} => {str(e)}\n{exc}")
    except Exception as e:  # pragma: no cover
        exc = traceback.format_exc()
        print(f"Exception indexing repository {log_url} => {str(e)}\n{exc}")

    return 0

def index_github_pull_requests(git_repo: Repository.Repository, show_progress: bool = False) -> int:
    n_requests = 0
    log_url = display_url(git_repo.clone_url)

    try:
        repo = ensure_repository(git_repo.clone_url, "github")
        if repo is None:
            log(f"### cannot create repostitory object for {log_url}")
            return 0

        if repo.is_active is False:
            log(f"### skipping inactive repository {log_url}")
            return 0

        log(f"starting to index merge requests for {log_url}")

        pull_requests = git_repo.get_pulls(state="closed")
        for pr in pull_requests:
            db_obj = MergeRequest.objects.filter(request_id=pr.number, repo=repo).first()
            if db_obj is not None:
                # merge request already indexed
                continue

            # TODO: pr.created_at is a naive datetime object, timezone is assumed to be UTC
            MergeRequest.objects.create(
                repo=repo,
                request_id=pr.number,
                state=pr.state,
                source_branch=pr.head.ref,
                target_branch=pr.base.ref,
                source_sha=pr.head.sha,  # does this value change when new commits are added to source branch?
                merge_sha=pr.merge_commit_sha,
                created_at=pr.created_at.replace(tzinfo=timezone.utc),
                merged_at=pr.merged_at.replace(tzinfo=timezone.utc),
                updated_at=pr.updated_at.replace(tzinfo=timezone.utc),
                # first_comment_at = ???
                is_merged=pr.merged,
                merged_by_username=pr.merged_by.login if pr.merged else None,
            ).save()

            n_requests += 1

            if n_requests > 0 and n_requests % 50 == 0 and show_progress:
                log(f"indexed {n_requests:3,} merge requests ")

        if n_requests > 0:
            log(f"indexed {n_requests:3,} merge requests in the repository")

        return n_requests

    except GitCommandError as e:
        print(f"{e._cmdline} returned {e.stderr} for {log_url}")
    except DatabaseError as e:
        exc = traceback.format_exc()
        print(f"DatabaseError indexing repository {log_url} => {str(e)}\n{exc}")
    except Exception as e:  # pragma: no cover
        exc = traceback.format_exc()
        print(f"Exception indexing repository {log_url} => {str(e)}\n{exc}")

    return 0

def update_commit_stats() -> None:
    """update stats at commit level"""
    log("updating commit stats")
    cursor = connection.cursor()
    for statement in STATS_SQL:
        try:
            cursor.execute(statement)
        except DatabaseError as e:
            exc = traceback.format_exc()
            print(f"Exception execute statement {statement} => {str(e)}\n{exc}")

def _new_commit_(git_commit: PyDrillerCommit) -> Commit:
    author, created = Author.objects.get_or_create(
        name=git_commit.committer.name.lower(),
        email=git_commit.committer.email.lower(),
    )
    if created:
        author.real_name = author.name
        author.real_email = author.email
        author.save()

    if is_aware(git_commit.committer_date):
        commit_dt = git_commit.committer_date
    else:
        commit_dt = make_aware(git_commit.committer_date)

    commit = Commit(
        sha=git_commit.hash,
        message=git_commit.msg[:2048],  # some commits has super long message, e.g. squash merge
        author=author,
        is_merge=git_commit.merge,
        branches=normalize_branches(git_commit.branches),
        n_lines=git_commit.lines,
        n_files=git_commit.files,
        n_insertions=git_commit.insertions,
        n_deletions=git_commit.deletions,
        # comment to save some time. metrics not used for now
        # dmm_unit_size=git_commit.dmm_unit_size,
        # dmm_unit_complexity=git_commit.dmm_unit_complexity,
        # dmm_unit_interfacing=git_commit.dmm_unit_interfacing,
        created_at=commit_dt,
    )
    commit.save()

    for mod in git_commit.modified_files:
        file_path = mod.new_path or mod.old_path
        flag = should_exclude_from_stats(file_path)
        new_file = CommittedFile(
            commit_sha=git_commit.hash,
            change_type=str(mod.change_type).split(".")[1],  # enum ModificationType.ADD => "ADD"
            file_path=file_path,
            file_name=mod.filename,
            n_lines_added=mod.added_lines,
            n_lines_deleted=mod.deleted_lines,
            n_lines_changed=mod.added_lines + mod.deleted_lines,
            n_lines_of_code=mod.nloc if mod.nloc else 0,
            n_methods=len(mod.methods),
            n_methods_changed=len(mod.changed_methods),
            is_on_exclude_list=flag,
            is_superfluous=flag,
            commit=commit,
        )
        new_file.save()

    return commit

def export_all_data(csv_file: str) -> None:
    cursor = connection.cursor()
    result = cursor.execute(QUERY_SQL["all_commit_data"])
    columns = [col[0] for col in cursor.description]

    n_rows = 0
    with open(csv_file, "w", newline="") as csvfile:
        writer = csv.writer(csvfile)
        writer.writerow(columns)
        for row in result.fetchall():
            n_rows += 1
            writer.writerow(row)
        log(f"exported {n_rows} rows to {csv_file}")


from django.shortcuts import render, redirect, reverse, get_object_or_404
from django.contrib import messages
from django.conf import settings
from django.core.mail import send_mail
from .forms import OrderForm
from cart.contexts import cart_contents
from sculptures.models import Sculpture
from .models import OrderLineItem, Order
import stripe

def checkout(request):
    """
    Checkout function, creates users order and calls stripe payments,
    Sends details through to the checkout_success view.
    """
    stripe_public_key = settings.STRIPE_PUBLIC_KEY
    stripe_secret_key = settings.STRIPE_SECRET_KEY
    try:
        if request.method == 'POST':
            cart = request.session.get('cart', {})
            form_data = {
                'full_name': request.POST['full_name'],
                'email': request.POST['email'],
                'phone_number': request.POST['phone_number'],
                'country': 'Ireland',
                'postcode': request.POST['postcode'],
                'town_or_city': request.POST['town_or_city'],
                'street_address1': request.POST['street_address1'],
                'street_address2': request.POST['street_address2'],
                'county': request.POST['county'],
            }
            order_form = OrderForm(form_data)
            if order_form.is_valid():
                order = order_form.save()
                for item_id, quantity in cart.items():
                    try:
                        product = get_object_or_404(Sculpture, id=item_id)
                        order_line_item = OrderLineItem(
                            order=order,
                            product=product,
                            quantity=quantity
                        )
                        order_line_item.save()
                    except Sculpture.DoesNotExist:
                        messages.error(request, (
                            'Something has gone wrong with your cart'
                            'Please call us for assistance')
                        )
                        order.delete()
                        return redirect(reverse('view_cart'))
                return redirect(
                    reverse('checkout_success', args=[order.order_number]))
            else:
                messages.error(
                    request,
                    'Something has gone wrong with your order,\
                        Double check your details.')
    except ValueError:
        message.error(request, 'Wait!')

    else:
        cart = request.session.get('cart', {})
        if not cart:
            messages.error(request, 'Your Cart is Empty!')
            return redirect(reverse('shop'))

        current_cart = cart_contents(request)
        total = current_cart['grand_total']
        stripe_total = round(total * 100)
        stripe.api_key = stripe_secret_key
        intent = stripe.PaymentIntent.create(
            amount=stripe_total,
            currency=settings.STRIPE_CURRENCY,
        )

    order_form = OrderForm()

    if not stripe_public_key:
        messages.warning(request, 'Stripe public key is missing!')

    context = {
        'form': order_form,
        'stripe_public_key': stripe_public_key,
        'client_secret': intent.client_secret,
    }
    return render(request, 'checkout/checkout.html', context)

def checkout_success(request, order_number):
    """
    Calls the order confirmation page,
    Updates stock quantity after successful purchase.
    Sends order confirmation email.
    """
    order = get_object_or_404(Order, order_number=order_number)
    messages.success(request, f'Payment successful! \
                                A confirmation email will been sent to \
                                {order.email}.')

    cart = request.session.get('cart', {})
    alter_quantity = []
    product_object = []
    number = 0
    email_intro = f"Hi {order.full_name},\n\nThank you for your order of: \n\n"
    email_outro = (
        f"\nWe will do our best to ship your items "
        + f"as soon as possible."
        + f"\nIf you have any questions about your order "
        + f"please feel free to get in touch. \n"
        + f"\nKind Regards, \n"
        + f"The Forged Nature Team")
    email_body = ""

    try:
        for item_id, quantity in cart.items():
            product_object.append(Sculpture.objects.get(id=item_id))
            alter_quantity.append(quantity)

            for item in product_object:
                if item.title not in email_body:
                    email_body += (
                        str(item.title) + f", Quantity : {quantity}, \n")

        email_intro = email_intro + email_body + email_outro

        send_mail(
            "Thank you for you order!",
            f"{email_intro}",
            settings.EMAIL_HOST_USER,
            [order.email],
            fail_silently=False,)

        for item in product_object:
            item.quantity = item.quantity - alter_quantity[number]
            item.save()
            number += 1
    except ValueError:
        messages.error(
            request, "Whoops, we've had a problem reaching our database.")

    del request.session['cart']
    context = {
        'order': order,
    }
    return render(request, 'checkout/checkout_success.html', context)


from __future__ import print_function
import pandas as pd
import numpy as np
import os
import time
import sys
from os.path import expanduser, join, exists
from os import remove
from journal_club.sound import *
from journal_club.jc_algorithm import algorithm
from journal_club import where_jc
from journal_club.__version__ import __version__

here = os.path.dirname(__file__)
countdown_mp3 = os.path.join(where_jc, 'countdown.wav')

def _input(x):
    try:
        return raw_input(x)
    except NameError:
        return input(x)

def save(r, record_csv, verbose=False):
    r.to_csv(record_csv)

def get_probs(record, attended=None):
    r = record.copy()
    p = algorithm(r, attended)
    return p

def validate_file(function):
    def inner(args, *a, **k):
        if not exists(args.record_csv):
            raise IOError("Record csv {} does not exist. Run `choose` at least once!".format(args.record_csv))

        record = get_record(args)
        cols = ['turn', 'attendence']
        missing = [i for i in cols if i not in record.columns]
        if missing:
            raise IOError("{} is not a valid record_csv file. {} columns are missing".format(args.record_csv, missing))
        return function(args, *a, **k)
    return inner

def create_new(args):
    names = list(set(args.attendences))
    print("Initialising with {}".format(','.join(names)))
    t = pd.DataFrame([[0, ','.join(names), '']], columns=['i', 'attendence', 'turn']).set_index('i')
    if os.path.exists(args.record_csv):
        yn = _input('overwrite previous records? (press enter to continue)')
        save(t, args.record_csv)
        print("overwriting records at {}".format(args.record_csv))
    else:
        save(t, args.record_csv)
        print("Created new record at {}".format(args.record_csv))

def import_legacy(record, args):
    last_picked = record.loc[record['meetings_since_last_turn'] == 0, 'name']
    print("Upgrading database...")
    record = create_new(args)
    record.loc[0] = [','.join(args.attendences), last_picked]
    return record

def get_record(args):
    try:
        record = pd.read_csv(args.record_csv).set_index('i')
        if 'name' in record.columns:
            record = import_legacy(record, args)
    except IOError:
        create_new(args)
        record = get_record(args)
    return record

def pretty_choose(record, attended, duration=5, freq=10):
    probs = get_probs(record, attended).iloc[-1]
    choices = np.random.choice(probs.index.values, p=probs.values, size=duration*freq)
    max_len = max(map(len, probs.index.values))
    times = np.arange(len(choices))**3.
    times /= times.sum()
    times *= duration

    template = '\rchoosing the next leader: {}'
    play_sound(countdown_mp3, 32-duration, block=False)
    previous = 0
    for i, (t, name) in enumerate(zip(times, choices)):
        time.sleep(t)
        if i == len(choices) - 1:
            template = "\r{}... your number's up"
        txt = template.format(name)
        sys.stdout.write(txt)
        gap = (previous - len(txt))
        if gap > 0:
            sys.stdout.write(' ' * gap)
        sys.stdout.flush()
        previous = len(txt)
    print()
    return name

def show_probabilities(probabilities):
    probs = probabilities.iloc[-1].sort_values(ascending=False)
    for i, (name, r) in enumerate(probs.iteritems()):
        print('{}. {} = {:.1%}'.format(i+1, name, r))

def choose(args):
    attend = args.attendences
    record = get_record(args)
    record.loc[len(record)] = [','.join(attend), '']
    probs = get_probs(record, attend)

    show_probabilities(probs)
    choice = pretty_choose(record, attend)

    record.loc[len(record)-1, 'turn'] = choice

    if not args.dry_run:
        save(record, args.record_csv)
    else:
        print("===DRYRUN===")
        show_probabilities(probs)
    play_text("{}, your number's up".format(choice))

@validate_file
def show(args):
    print("Accessing database at {}".format(args.record_csv))
    record = get_record(args)
    probs = get_probs(record)
    included = args.include if args.include else probs.columns
    excluded = args.exclude if args.exclude else []
    names = list(set(included) - set(excluded))
    show_probabilities(probs[names])

@validate_file
def validate(args):
    print("{} is a valid journal_club record_csv file.".format(args.record_csv))

@validate_file
def reset(args):
    input("Warning! Removing database file! Press ENTER to continue/ctrl+c to cancel ")
    remove(args.record_csv)
    print("File removed...")

def test(args):
    play_text('This is a test')
    duration = 3
    play_sound(countdown_mp3, 32-duration, block=False)
    time.sleep(duration)
    print("Test finished")

def main():
    import argparse
    parser = argparse.ArgumentParser('jc')
    subparsers = parser.add_subparsers()

    home = expanduser("~")
    default_location = os.path.join(home, 'jc_record.csv')
    parser.add_argument('--record_csv', default=default_location, help='Record file location default={}'.format(default_location))
    parser.add_argument('--verbose', action='store_true', help='show all messages')
    parser.add_argument('--debug', help='Shows error messages, tracebacks and exceptions. Not normally needed', action="store_true")

    show_parser = subparsers.add_parser('show', help='Shows the current record state')
    show_parser.add_argument('--include', nargs='+', default=[], help='The people to be included')
    show_parser.add_argument('--exclude', nargs='+', default=[], help='The people to be excluded')
    show_parser.set_defaults(func=show)

    version_parser = subparsers.add_parser('version', help='Shows the current version and exits')
    version_parser.set_defaults(func=lambda x: print(__version__))

    choose_parser = subparsers.add_parser('choose', help='Run the choosertron and pick a person from the given list (attendences). '\
                                                          'Creates database if needed')
    choose_parser.add_argument('attendences', nargs='+', help='The people that are in attendence')
    choose_parser.add_argument('--dry-run', action='store_true', help="Don't save the result")
    choose_parser.set_defaults(func=choose)

    subparsers.add_parser('reset', help='Deletes the record file. Runs `rm RECORD_CSV`').set_defaults(func=reset)
    subparsers.add_parser('validate', help='Validates the record file.').set_defaults(func=validate)
    subparsers.add_parser('soundtest', help='tests the sound playback').set_defaults(func=test)

    args = parser.parse_args()
    if hasattr(args, 'func'):
        if args.debug:
            args.func(args)
        else:
            try:
                args.func(args)
            except Exception as e:
                print("Improper usage of `jc'. Look at the help")
                print("Error message reads: {}".format(str(e)))
                parser.print_help()
                raise e
    else:
        parser.print_help()

if __name__ == '__main__':
    main()


from scipy.stats import norm
import h5py
import pandas as pd
import sys
import os
import yaml
import matplotlib.pyplot as plt
import numpy as np
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
import utils
plt.rcParams['axes.spines.top'] = False
plt.rcParams['axes.spines.right'] = True
plt.rcParams['font.size'] = 18

config_path = os.path.join(os.path.dirname(__file__), '../../config.yml')
cfg = yaml.safe_load(open(config_path, 'r'))
spike_dt = 0.001
if __name__=='__main__':
    example_filename = '../../data/intermediate/rockstar.p'
    example_trial = 234
    example_filename = '../../data/intermediate/mack.p'
    example_trial = 43
    example_filename = '../../data/intermediate/raju-M1-no-bad-trials.p'
    example_trial = 121

    run_info = yaml.safe_load(open(os.path.dirname(__file__) + '/../../lfads_file_locations.yml', 'r'))
    datasets = list(run_info.keys())
    example_filenames = ['../../data/intermediate/%s.p'%dataset for dataset in datasets]
    example_trials = [234, 43, 121]
    example_filenames = [example_filenames[0]]
    example_trials = [45]
    for idx, (example_filename, example_trial) in enumerate(zip(example_filenames, example_trials)):
        df = pd.read_pickle(example_filename)
        trial_df = df.loc[example_trial].loc[:4]
        x_vel, y_vel = trial_df.kinematic[['x_vel','y_vel']].values.T
        speed = utils.get_speed(x_vel, y_vel)
        sigma_bin = cfg['rate_sigma_example_plot']
        bin_cutoff = sigma_bin*5 #cutoff kernal at 5 sigmas
        neural_kernel = norm.pdf(np.arange(-bin_cutoff, bin_cutoff), scale=sigma_bin)
        neural_kernel /= np.sum(neural_kernel)*spike_dt
        fr = np.apply_along_axis(np.convolve, 0, trial_df.neural.values, neural_kernel, mode='same')
        trial_pop_rate = fr.sum(1)
        t = trial_df.index.values
        t_targets = trial_df.kinematic.query('hit_target').index.values
        lns = []
        plt.figure(figsize=(18,6))
        lns += plt.plot(t, speed,'g')
        plt.ylabel('Cursor Speed (mm/s)')
        plt.xlabel('Time (s)')
        # plt.twinx()
        # lns += plt.plot(t, trial_pop_rate)
        # plt.ylabel('Population Firing Rate (spikes/s)')
        ymin, ymax = plt.ylim()
        plt.vlines(t_targets[1:], ymin, ymax)
        plt.text(t_targets[1]+0.02, ymax*.915, "Target 1\nAcquired", fontsize=12)
        for i,t in enumerate(t_targets[2:-1]):
            plt.text(t+0.01, ymax*.95, "Target %d"%(i+2), fontsize=12)

        if idx == 0:
            plt.arrow(2.02, 45, .10, 40, head_width=.02, head_length=5)
            plt.text(1.65, 30, "Corrective Movement", fontsize=12)
        # plt.legend(handles=lns, labels=['Cursor Speed', 'Population Firing Rate'],
        #            bbox_to_anchor=(0, 1.02, 1, 0.2), loc='lowerleft', ncol=3)
            plt.savefig('../../figures/final_figures/example_trial.png')
        plt.savefig('../../figures/final_figures/numbered/6a%d.png'%(idx+1))
        plt.savefig('../../figures/final_figures/numbered/6a%d.png'%(idx+1))
        plt.savefig('../../figures/final_figures/numbered/6a%d.png'%(idx+1))
    plt.show()


from utils import my_parser, seed_util, wb_util
from models.my_model import Encoder
import torch
from loaders import v3_triplet_loader
from utils.eval_shortcut import Cut
from utils import eva_emb_full

def do_step(epoch, step, data):
    optimizer.zero_grad()
    data = [i.cuda() for i in data]
    v1, f1, v2, f2 = data
    v_emb1, f_emb1 = model(v1, f1)
    v_emb2, f_emb2 = model(v2, f2)
    loss = loss_fun(v_emb1, f_emb1, f_emb2) + loss_fun(f_emb1, v_emb1, v_emb2)
    loss.backward()
    optimizer.step()
    return loss.item(), {}

def train():
    step = 0
    model.train()

    for epo in range(args.epoch):
        wb_util.log({"train/epoch": epo})
        for data in train_iter:
            loss, info = do_step(epo, step, data)
            step += 1
            if step % 50 == 0:
                obj = {
                    "train/step": step,
                    "train/loss": loss,
                }
                obj = {**obj, **info}
                print(obj)
                wb_util.log(obj)

            if step > 0 and step % args.eval_step == 0:
                if eval_cut.eval_short_cut():
                    return

if __name__ == "__main__":
    parser = my_parser.MyParser(epoch=100, batch_size=256, model_save_folder="./outputs/", early_stop=10)
    parser.custom({
        "batch_per_epoch": 500,
        "eval_step": 100,
        "margin": 1.0,
        "loss": ""
    })
    parser.use_wb("VFALBenchmark", "triplet loss")
    args = parser.parse()
    seed_util.set_seed(args.seed)
    train_iter = v3_triplet_loader.get_iter(args.batch_size, args.batch_per_epoch * args.batch_size)

    # model
    model = Encoder().cuda()

    loss_fun = torch.nn.TripletMarginLoss(margin=args.margin, p=2)

    model_params = model.parameters()
    optimizer = torch.optim.Adam(model_params, lr=args.lr)
    emb_eva = eva_emb_full.EmbEva()
    eval_cut = Cut(emb_eva, model, args)
    train()


import json
import cv2, math

# json 생성
def make_json_file(pill_shape, pill_text, pill_line=False):
    # json 기본 구조 설정
    drugData = {
        "drug_name" : "",
        "drug_type" : "",
        "drug_shape" : "",
        "drug_color" : "",
        "drug_line" : "없음"
    }
    drugData['drug_shape'] = pill_shape # 알약 모양
    drugData["drug_name"] = pill_text # 알약 문자
    # drugData["drug_line"] = pill_line
    try:
        # 알약 특징 정보를 가진 json 파일 생성
        with open('pill_data.json', 'w') as pill_data:
            json.dump(drugData, pill_data, indent='\t')
    except:
        print("error")

# crop한 text영역 이미지 회전 (croped img, polys=영역 좌표, 중심좌표)
def rotate_img(img, polys, center):
    center = tuple(map(int, center))
    # 좌상단(polys[0]), 좌하단(polys[3])
    # 좌하단을 기준으로 절대각도 확인
    x1, y1 = polys[0]
    x3, y3 = polys[2]
    x4, y4 = polys[3]
    dx, dy = x4 - x3, y4 - y3
    dhx, dhy = x4 - x1, y4 - y1
    angle = math.degrees(math.atan2(dy, dx))
    # text영역 크기 지정
    imgW = int(math.sqrt((dx**2)+(dy**2)))
    imgH = int(math.sqrt((dhx**2)+(dhy**2)))

    if dx < 0:
        angle += 180

    height, width, channels = img.shape
    M = cv2.getRotationMatrix2D(center, angle, 1)
    # atan2로 구한 각도에 맞추어 회전
    img_rot = cv2.warpAffine(img, M, (width, height))
    # text 영역 중심을 기준으로 이미지 crop
    img_crop = cv2.getRectSubPix(img_rot, (imgW,imgH), center)
    if imgH > (imgW*1.3):
        img_crop = cv2.rotate(img_crop, cv2.ROTATE_90_CLOCKWISE)

    return img_crop


EXPOUT = "@variables{c:#0f0}a{color:var(c)}\na{color:#0f0}\n"
EXPERR = 'Property: Found valid "CSS Level 2.1" value: #0f0 [6:9: color]\n'

def main():
    import cssutils
    import cssutils.script

    css = '''
    @variables {
        c: #0f0;
    }
    a {
        color: var(c);
    }
    '''
    s = cssutils.parseString(css)

    cssutils.ser.prefs.useMinified()
    cssutils.ser.prefs.resolveVariables = False
    print(s.cssText)

    # reset
    cssutils.ser.prefs.resolveVariables = True
    print(s.cssText)

if __name__ == '__main__':
    main()


'https://arxiv.org/pdf/2206.07293.pdf'

import torch
import torch.nn as nn
import torch.nn.functional as F

from conv_stft import ConvSTFT, ConviSTFT
from modules.complex_nn import ComplexConv2d, ComplexConvTranspose2d, ComplexLSTM, ComplexBatchNorm2d, complex_concat
from modules import CCBAM

class ConvBlock(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, padding=(0, 0), norm=True, act=True, causal=True, is_complex=True, **kwargs) -> None:
        super().__init__()

        self.causal = causal
        self.padding = padding
        negative_slope = kwargs.get('negative_slope', 0.2)

        if is_complex:
            self.conv = ComplexConv2d(in_channels, out_channels, kernel_size, padding=(self.padding[0], 0), bias=not norm, **kwargs)
            self.norm = ComplexBatchNorm2d(out_channels) if norm else nn.Identity()
            self.act = nn.LeakyReLU(negative_slope) if act else nn.Identity()
        else:
            self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, padding=(self.padding[0], 0), bias=not norm, **kwargs)
            self.norm = nn.BatchNorm2d(out_channels) if norm else nn.Identity()
            self.act = nn.LeakyReLU(negative_slope) if act else nn.Identity()

    def forward(self, x):
        if self.causal:
            x = F.pad(x, (self.padding[1], 0, 0, 0))
        else:
            x = F.pad(x, (self.padding[1], self.padding[1], 0, 0))

        return self.act(self.norm(self.conv(x)))

class ConvTransposeBlock(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, padding=(0, 0), norm=True, act=True, causal=True, is_complex=True, **kwargs) -> None:
        super().__init__()

        self.causal = causal
        self.padding = padding
        negative_slope = kwargs.get('negative_slope', 0.2)

        if is_complex:
            self.conv_transposed = ComplexConvTranspose2d(in_channels, out_channels, kernel_size, padding=(self.padding[0], 0), bias=not norm, **kwargs)
            self.norm = ComplexBatchNorm2d(out_channels) if norm else nn.Identity()
            self.act = nn.LeakyReLU(negative_slope) if act else nn.Identity()
        else:
            self.conv_transposed = nn.ConvTranspose2d(in_channels, out_channels, kernel_size, padding=(self.padding[0], 0), bias=not norm, **kwargs)
            self.norm = nn.BatchNorm2d(out_channels) if norm else nn.Identity()
            self.act = nn.LeakyReLU(negative_slope) if act else nn.Identity()

    def forward(self, x):
        if self.causal:
            x = F.pad(x, (self.padding[1], 0, 0, 0))
        else:
            x = F.pad(x, (self.padding[1], self.padding[1], 0, 0))

        return self.act(self.norm(self.conv_transposed(x)))

class Encoder(nn.Module):
    def __init__(self, in_channels=1, out_channels=128, num_repeats=6, is_complex=True) -> None:
        super().__init__()

        self.layers = nn.ModuleList()
        for _ in range(num_repeats):
            self.layers += [ConvBlock(in_channels, out_channels, kernel_size=(5, 2), stride=(2, 1), padding=(0, 1), causal=True, is_complex=is_complex)]
            in_channels = out_channels

    def forward(self, x):
        outputs = []
        for layer in self.layers:
            x = layer(x)
            outputs.append(x)
        return x, outputs

class Decoder(nn.Module):
    def __init__(self, in_channels=128, out_channels=128, num_repeats=6, reduction_ratio=16, is_complex=True) -> None:
        super().__init__()

        self.skip_connection_attention_layers = nn.ModuleList()
        self.layers = nn.ModuleList()

        for i in range(num_repeats):
            self.skip_connection_attention_layers += [CCBAM(in_channels, reduction_ratio)]
            self.layers += [ConvTransposeBlock(in_channels * 2, out_channels, kernel_size=(5, 2), stride=(2, 1), padding=(0, 0), causal=True, is_complex=is_complex)]
            in_channels = out_channels

    def forward(self, x, encoder_outputs):
        for i, layer in enumerate(self.layers):
            encoder_output = encoder_outputs.pop()
            encoder_output = self.skip_connection_attention_layers[i](encoder_output)

            if x.shape[-1] > encoder_output.shape[-1]:
                x = x[:, :, :, :-1]
            if x.shape[-2] < encoder_output.shape[-2]:
                x = F.pad(x, (0, 0, 0, 1))

            x = complex_concat([x, encoder_output], dim=1)
            x = layer(x)
        return x

class FRCRN(nn.Module):
    def __init__(self, window_size=320, hop_size=160, fft_size=640, lstm_channels=256, reduction_ratio=16, is_complex=True) -> None:
        super().__init__()

        self.stft = ConvSTFT(window_size, hop_size, fft_size)
        self.istft = ConviSTFT(window_size, hop_size, fft_size)

        self.encoder = Encoder(in_channels=2, out_channels=128, is_complex=is_complex)
        self.decoder = Decoder(in_channels=128, out_channels=128, is_complex=is_complex, reduction_ratio=reduction_ratio)
        self.lstm = ComplexLSTM(256, lstm_channels, num_layers=2, bidirectional=False, batch_first=True)

        self.final_conv = nn.Conv2d(128, 2, kernel_size=(1, 2), bias=False)

        self.fft_size = fft_size

    def forward(self, x):
        ### stft
        complex_specs = self.stft(x)

        noisy_real = complex_specs[:, :self.fft_size // 2 + 1, :]
        noisy_imag = complex_specs[:, self.fft_size // 2 + 1:, :]

        complex_specs = torch.stack([noisy_real, noisy_imag], dim=1)
        complex_specs = complex_specs[:, :, 1:, :]

        ### process
        x = complex_specs
        x, encoder_outputs = self.encoder(x)

        batch, channels, freq, time = x.shape
        x = x.view(batch, channels * freq, time).permute(0, 2, 1)

        x = self.lstm(x)
        x = x.permute(0, 2, 1).view(batch, channels, freq, time)

        x = self.decoder(x, encoder_outputs)
        x = self.final_conv(x)
        x = F.pad(x, (0, 0, 1, 0))

        ### masking
        mask = torch.tanh(x)
        clean_estimate_spec = mask * complex_specs
        clean_estimate_spec = F.pad(clean_estimate_spec, (0, 0, 1, 0))

        ### istft
        real = clean_estimate_spec[:, 0, :, :]
        imag = clean_estimate_spec[:, 1, :, :]

        clean_estimate_spec = torch.cat([real, imag], dim=1)
        clean_estimate_wav = self.istft(clean_estimate_spec)
        clean_estimate_wav = torch.clamp_(clean_estimate_wav, -1, 1)
        return clean_estimate_spec, clean_estimate_wav

if __name__ == '__main__':
    # TODO 1: FSMN, CFSMN

    win_size = 320  # = 16000Hz * 0.02s (20ms) in paper
    hop_size = 160  # = 16000Hz * 0.01s (10ms) in paper
    fft_size = 640

    signal = torch.randn(1, 16000)
    model = FRCRN(win_size, hop_size, fft_size, is_complex=True)

    clean_estimate_spec, clean_estimate_wav = model(signal)
    print(clean_estimate_wav.shape)