"""Script to render a video using a trained pi-GAN  model."""

import argparse
import math
import os

from torchvision.utils import save_image

import torch
import numpy as np
from PIL import Image
from tqdm import tqdm
import numpy as np
import skvideo.io
import curriculums

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

parser = argparse.ArgumentParser()
parser.add_argument('path', type=str)
parser.add_argument('--seeds', nargs='+', default=[0, 1, 2])
parser.add_argument('--output_dir', type=str, default='vids')
parser.add_argument('--batch_size', type=int, default=1)
parser.add_argument('--max_batch_size', type=int, default=2400000)
parser.add_argument('--depth_map', action='store_true')
parser.add_argument('--lock_view_dependence', action='store_true')
parser.add_argument('--image_size', type=int, default=256)
parser.add_argument('--ray_step_multiplier', type=int, default=2)
parser.add_argument('--num_frames', type=int, default=36)
parser.add_argument('--curriculum', type=str, default='CelebA')
parser.add_argument('--trajectory', type=str, default='front')
opt = parser.parse_args()

os.makedirs(opt.output_dir, exist_ok=True)

curriculum = getattr(curriculums, opt.curriculum)
curriculum['num_steps'] = curriculum[0]['num_steps'] * opt.ray_step_multiplier
curriculum['img_size'] = opt.image_size
curriculum['psi'] = 0.7
curriculum['v_stddev'] = 0
curriculum['h_stddev'] = 0
curriculum['lock_view_dependence'] = opt.lock_view_dependence
curriculum['last_back'] = curriculum.get('eval_last_back', False)
curriculum['num_frames'] = opt.num_frames
curriculum['nerf_noise'] = 0
curriculum = {key: value for key, value in curriculum.items() if type(key) is str}

def tensor_to_PIL(img):
    img = img.squeeze() * 0.5 + 0.5
    return Image.fromarray(img.mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 0).to('cpu', torch.uint8).numpy())

generator = torch.load(opt.path, map_location=device)
ema_file = opt.path.split('generator')[0] + 'ema.pth'
ema = torch.load(ema_file, map_location=device)
ema.copy_to(generator.parameters())
generator.set_device(device)
generator.eval()

if opt.trajectory == 'front':
    trajectory = []
    for t in np.linspace(0, 1, curriculum['num_frames']):
        pitch = 0.2 * np.cos(t * 2 * math.pi) + math.pi/2
        yaw = 0.4 * np.sin(t * 2 * math.pi) + math.pi/2
        fov = 12

        fov = 12 + 5 + np.sin(t * 2 * math.pi) * 5

        trajectory.append((pitch, yaw, fov))
elif opt.trajectory == 'orbit':
    trajectory = []
    for t in np.linspace(0, 1, curriculum['num_frames']):
        pitch = math.pi/4
        yaw = t * 2 * math.pi
        fov = curriculum['fov']

        trajectory.append((pitch, yaw, fov))

for seed in opt.seeds:
    frames = []
    depths = []
    output_name = f'{seed}.mp4'
    writer = skvideo.io.FFmpegWriter(os.path.join(opt.output_dir, output_name), outputdict={'-pix_fmt': 'yuv420p', '-crf': '21'})

    torch.manual_seed(seed)
    z = torch.randn(1, 256, device=device)

    with torch.no_grad():
        for pitch, yaw, fov in tqdm(trajectory):
            curriculum['h_mean'] = yaw
            curriculum['v_mean'] = pitch
            curriculum['fov'] = fov
            curriculum['h_stddev'] = 0
            curriculum['v_stddev'] = 0

            frame, depth_map = generator.staged_forward(z, max_batch_size=opt.max_batch_size, depth_map=opt.depth_map, **curriculum)
            frames.append(tensor_to_PIL(frame))

        for frame in frames:
            writer.writeFrame(np.array(frame))

        writer.close()


"""
Contact sites of protein-DNA interaction
========================================

This script identifies which amino acids of the phage 434 repressor are
in contact with its corresponding DNA operator.
*In contact* is defined as a pairwise atom distance below a given
threshold (in this case 4.0 Å).

The identified contact residues are highlighted as sticks.
"""

# Code source: Patrick Kunzmann
# License: BSD 3 clause

import numpy as np
import biotite.structure as struc
import biotite.structure.io.mmtf as mmtf
import biotite.database.rcsb as rcsb

# The maximum distance between an atom in the repressor and an atom in
# the DNA for them to be considered 'in contact'
THRESHOLD_DISTANCE = 4.0

# Fetch and load structure
mmtf_file = mmtf.MMTFFile.read(rcsb.fetch("2or1", "mmtf"))
structure = mmtf.get_structure(mmtf_file, model=1)

# Separate structure into the DNA and the two identical protein chains
dna = structure[
    np.isin(structure.chain_id, ["A", "B"]) & (structure.hetero == False)
]
protein_l = structure[
    (structure.chain_id == "L") & (structure.hetero == False)
]
protein_r = structure[
    (structure.chain_id == "R") & (structure.hetero == False)
]
# Quick check if the two protein chains are really identical
assert len(struc.get_residues(protein_l)) == len(struc.get_residues(protein_r))

# Fast identification of contacts via a cell list:
# The cell list is initiliazed with the coordinates of the DNA
# and later provided with the atom coordinates of the two protein chains
cell_list = struc.CellList(dna, cell_size=THRESHOLD_DISTANCE)

# Sets to store the residue IDs of contact residues
# for each protein chain
id_set_l = set()
id_set_r = set()

for protein, res_id_set in zip((protein_l, protein_r), (id_set_l, id_set_r)):
    # For each atom in the protein chain,
    # find all atoms in the DNA that are in contact with it
    contacts = cell_list.get_atoms(protein.coord, radius=THRESHOLD_DISTANCE)
    # Only retain atoms in the protein with contact
    # to at least one atom of the DNA
    contact_indices = np.where((contacts != -1).any(axis=1))[0]
    # Get residue IDs for the atoms in the protein
    contact_res_ids = protein.res_id[contact_indices]
    # Put the residue IDs into the set,
    # duplicate IDs are automatically removed in this process
    res_id_set.update(contact_res_ids)

# Only consider residues that show contacts in both peptide chains
# -> intersection of sets
common_ids = sorted(id_set_l & id_set_r)

# Print output
print("Residues in contact with DNA:")
print()
for res_id in common_ids:
    res_name = protein_l.res_name[protein_l.res_id == res_id][0]
    print(res_name.capitalize() + str(res_id))

# Visualization with PyMOL...


from __future__ import print_function

import os
import argparse
import itertools
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import precision_recall_fscore_support, confusion_matrix

from dcase_task2.eval import print_result
from dcase_task2.utils.apk import mapk
from dcase_task2.utils.data_tut18_task2 import CLASS_ID_MAPPING, ID_CLASS_MAPPING
from dcase_task2.config.settings import DATA_ROOT as DATA_ROOT

def load_gt(lb_set="private"):
    """
    Load files of leaderboard splits
    """

    lb_set_list = lb_set.split("_")

    # load post competition ground truth
    with open(os.path.join(DATA_ROOT, "test_post_competition.csv"), 'r') as fp:
        train_list = fp.read()

    files = []
    labels = []
    for i, line in enumerate(train_list.split("\n")):

        if i == 0 or line == '':
            continue

        split_line = line.split(",")

        file_usage = split_line[2].lower()
        if file_usage not in lb_set_list:
            continue

        if split_line[0] != '':
            file_name = split_line[0].strip()

            files.append(file_name)
            labels.append(CLASS_ID_MAPPING[split_line[1].strip()])

        else:
            pass

    return np.asarray(files, dtype=np.string_), np.asarray(labels, dtype=np.int32)

def load_prediction(submission_file, file_list=None):
    """
    Load prediction (submission file)
    """
    with open(submission_file, 'r') as fp:
        train_list = fp.read()

    files = []
    labels = []
    for i, line in enumerate(train_list.split("\n")):

        if i == 0 or line == '':
            continue

        split_line = line.split(",")

        if split_line[0] != '':
            file_name = split_line[0].strip()

            if file_list is not None and file_name not in file_list:
                continue

            files.append(file_name)
            labels.append([CLASS_ID_MAPPING[l] for l in split_line[1].strip().split(" ")])

        else:
            pass

    return np.asarray(files, dtype=np.string_), np.asarray(labels, dtype=np.int)

def plot_confusion_matrix(preds, truth, normalize=False):
    """
    Create confusion matrix plot
    """

    cnf_matrix = confusion_matrix(truth, preds)

    # Concatenate labels of prediction and true arrays
    labels = list(np.unique(truth))
    for p in np.unique(preds):
        try:
            labels.index(p)
        except:
            labels.append(p)
    labels = sorted(labels)

    # Normalize confusion matrix
    if normalize:
        cnf_matrix = cnf_matrix.astype('float') / cnf_matrix.sum(axis=1)[:, np.newaxis]

    # plot confusion matrix
    plt.figure("Confusion Matrix", figsize=(15, 15))
    plt.clf()
    plt.subplot(111)
    plt.subplots_adjust(bottom=0.13, top=0.98, right=0.99)
    plt.imshow(cnf_matrix, interpolation='nearest', cmap=plt.cm.Blues)
    plt.title('Confusion Matrix')
    tick_marks = np.arange(len(labels))
    if len(labels) > 2:
        labels = [ID_CLASS_MAPPING[l] for l in labels]
    plt.xticks(tick_marks, labels, rotation="vertical")
    plt.yticks(tick_marks, labels)

    fmt = '.2f' if normalize else 'd'
    thresh = cnf_matrix.max() / 2.
    for i, j in itertools.product(range(cnf_matrix.shape[0]), range(cnf_matrix.shape[1])):
        plt.text(j, i, format(cnf_matrix[i, j], fmt),
                 horizontalalignment="center",
                 color="white" if cnf_matrix[i, j] > thresh else "black")

    # plt.tight_layout()
    plt.ylabel('True label')
    plt.xlabel('Predicted label')

if __name__ == '__main__':
    """ main """

    # add argument parser
    parser = argparse.ArgumentParser(description='Evaluate leaderboard submission.')
    parser.add_argument('--submission_file', help='path to submission file.', default=None)
    parser.add_argument('--set', help='leaderboard set (private, public, private_public).', type=str, default="private")
    args = parser.parse_args()

    gt_files, gt_labels = load_gt(lb_set=args.set)
    pr_files, pr_labels = load_prediction(submission_file=args.submission_file, file_list=gt_files)

    sorted_idx = np.argsort(gt_files)
    gt_files = gt_files[sorted_idx]
    gt_labels = gt_labels[sorted_idx]

    sorted_idx = np.argsort(pr_files)
    pr_files = pr_files[sorted_idx]
    pr_labels = pr_labels[sorted_idx]

    # print results to command line
    print_result(None, gt_labels, pr_labels[:, 0], ID_CLASS_MAPPING)
    actual = [[y] for y in gt_labels]
    print("MAP@%d: %.5f" % (3, mapk(actual, pr_labels, 3)))
    print('=' * 70)

    # create some evaluation plots
    print("Creating f-score plot (fscores.pdf)")
    n_classes = len(np.unique(y))
    p, r, f, s = precision_recall_fscore_support(gt_labels, pr_labels[:, 0], labels=None, pos_label=1, average=None)
    plt.figure("F-Score", figsize=(7, 4))
    plt.clf()
    plt.subplots_adjust(bottom=0.45, left=0.08, right=0.99, top=0.99)
    ax = plt.subplot(111)
    plt.bar(range(41), f)
    plt.xlim([-1, 41])
    ax.yaxis.grid(True)
    plt.ylabel("F-Score")
    plt.xticks(np.arange(41), [ID_CLASS_MAPPING[l].replace("_", " ") for l in range(41)], rotation='vertical')
    plt.savefig("fscores.pdf")

    print("Creating confusion matrix plot (cmatrix.pdf)")
    plot_confusion_matrix(pr_labels[:, 0], gt_labels)
    plt.savefig("cmatrix.pdf")


# Sound Source locate
#
# @Time    : 2019-10-11 18:51
# @Author  : xyzhao
# @File    : game.py
# @Description: A platform to perform reinforcement learning

import numpy as np
import collections
import math
import pickle
from walker import Walker

"""
    env computing, reward computing
    game play settings

"""

class Game:
    def __init__(self):
        self.n_features = 366
        self.n_actions = 8
        self.max_epoch = 100
        self.max_steps = 100

        # define sound source information
        self.src_pos_x = -3.0
        self.src_pos_y = 1.6
        self.src_pos_z = -3.0

        # sample as a grid map with 0.5m unit
        self.unit = 0.5
        self.room_grids = [i for i in np.arange(-3.5, 3.5 + self.unit, self.unit)]

        self.walker = Walker(self.n_features, self.n_actions)

    def detect_invalids(self, x, y, z):
        invalids = []
        if x == 3.5:
            invalids.append(self.walker.action_labels.index('90'))
            invalids.append(self.walker.action_labels.index('45'))
            invalids.append(self.walker.action_labels.index('135'))
        if x == -3.5:
            invalids.append(self.walker.action_labels.index('270'))
            invalids.append(self.walker.action_labels.index('225'))
            invalids.append(self.walker.action_labels.index('315'))
        if z == 3.5:
            invalids.append(self.walker.action_labels.index('180'))
            invalids.append(self.walker.action_labels.index('135'))
            invalids.append(self.walker.action_labels.index('225'))
        if z == -3.5:
            invalids.append(self.walker.action_labels.index('0'))
            invalids.append(self.walker.action_labels.index('315'))
            invalids.append(self.walker.action_labels.index('45'))

        obstable_x = [-1, -0.5, 0, 0.5, 1]
        obstable_z = [-1, -0.5, 0, 0.5, 1]

        if x == 1.5 and z == 1.5:
            invalids.append(self.walker.action_labels.index('315'))
        elif x == 1.5 and z == 1:
            invalids.append(self.walker.action_labels.index('315'))
            invalids.append(self.walker.action_labels.index('270'))
        elif x == 1.5 and z in np.arange(-0.5, 1, 0.5):
            invalids.append(self.walker.action_labels.index('315'))
            invalids.append(self.walker.action_labels.index('270'))
            invalids.append(self.walker.action_labels.index('225'))
        elif x == 1.5 and z == -1:
            invalids.append(self.walker.action_labels.index('225'))
            invalids.append(self.walker.action_labels.index('270'))
        elif x == 1.5 and z == -1.5:
            invalids.append(self.walker.action_labels.index('225'))
        elif x == 1 and z == -1.5:
            invalids.append(self.walker.action_labels.index('225'))
            invalids.append(self.walker.action_labels.index('180'))
        elif x in np.arange(-0.5, 1, 0.5) and z == -1.5:
            invalids.append(self.walker.action_labels.index('225'))
            invalids.append(self.walker.action_labels.index('180'))
            invalids.append(self.walker.action_labels.index('135'))
        elif x == -1 and z == -1.5:
            invalids.append(self.walker.action_labels.index('180'))
            invalids.append(self.walker.action_labels.index('135'))
        elif x == -1.5 and z == -1.5:
            invalids.append(self.walker.action_labels.index('135'))
        elif x == -1.5 and z == -1:
            invalids.append(self.walker.action_labels.index('90'))
            invalids.append(self.walker.action_labels.index('135'))
        elif x == -1.5 and z in np.arange(-0.5, 1, 0.5):
            invalids.append(self.walker.action_labels.index('90'))
            invalids.append(self.walker.action_labels.index('135'))
            invalids.append(self.walker.action_labels.index('45'))
        elif x == -1.5 and z == 1:
            invalids.append(self.walker.action_labels.index('45'))
            invalids.append(self.walker.action_labels.index('90'))
        elif x == -1.5 and z == 1.5:
            invalids.append(self.walker.action_labels.index('45'))
        elif x == -1 and z == 1.5:
            invalids.append(self.walker.action_labels.index('0'))
            invalids.append(self.walker.action_labels.index('45'))
        elif x in np.arange(-0.5, 1, 0.5) and z == 1.5:
            invalids.append(self.walker.action_labels.index('315'))
            invalids.append(self.walker.action_labels.index('0'))
            invalids.append(self.walker.action_labels.index('45'))
        elif x == 1 and z == 1.5:
            invalids.append(self.walker.action_labels.index('315'))
            invalids.append(self.walker.action_labels.index('0'))

        # todo, abstract an obstacle
        return invalids

    def play(self):
        records_step = []
        records_r = []

        for epoch in range(self.max_epoch):
            print("========== Epoch %d ======" % epoch)
            memory = collections.defaultdict(dict)
            visit = {}
            for i in self.room_grids:
                for j in self.room_grids:
                    visit[str(i) + "*" + str(j)] = 0
                    for k in self.walker.action_labels:
                        memory[str(i) + "*" + str(j)][k] = 0

            # init walker position
            # fixme, random choose
            self.walker.reset_walker_pos(3.0, 1, 3.0)
            DONE = False
            sum_reward = 0.0

            a_his = None
            for step in range(self.max_steps):
                s = self.walker.observe_gcc_vector(self.walker.pos_x, self.walker.pos_y, self.walker.pos_z)
                s = np.array(s)[np.newaxis, :]

                # fixme, use grids to detect
                # fixme, cut action space
                invalids = self.detect_invalids(self.walker.pos_x, self.walker.pos_y, self.walker.pos_z)

                pos_key = str(self.walker.pos_x) + "*" + str(self.walker.pos_z)
                for i in memory[pos_key].keys():
                    if memory[pos_key][i] >= 2:
                        invalids.append(self.walker.action_labels.index(i))
                visit[pos_key] += 1

                a, p = self.walker.choose_action(s, invalids)

                # step next state
                direction = self.walker.action_labels[a]

                # fixme, for the first step, give more obs, argmax
                if step == 0:
                    fe = open('first_obs.pkl', 'rb')
                    obs = pickle.load(fe)

                    s_r = obs['right']
                    s_r = np.array(s_r)[np.newaxis, :]
                    a_r, p_r = self.walker.choose_action(s_r, [])
                    p_rr = [p_r[len(p_r) - 2], p_r[len(p_r) - 1]]
                    p_rr = np.append(p_rr, p_r[:len(p_r) - 2])

                    s_l = obs['left']
                    s_l = np.array(s_l)[np.newaxis, :]
                    a_l, p_l = self.walker.choose_action(s_l, [])
                    p_ll = [p_l[0], p_l[1]]
                    p_ll = np.append(p_l[2:], p_ll)

                    s_d = obs['down']
                    s_d = np.array(s_d)[np.newaxis, :]
                    a_d, p_d = self.walker.choose_action(s_d, [])
                    p_dd = [p_d[len(p_d) - 4], p_d[len(p_d) - 3], p_d[len(p_d) - 2], p_d[len(p_d) - 1]]
                    p_dd = np.append(p_dd, p_d[:len(p_d) - 4])

                    # fixme, define first step based on obs, do argmax
                    p_mix = [0] * self.n_actions
                    for i in range(self.n_actions):
                        if i not in invalids:
                            p_mix[i] = p[i] + p_rr[i] + p_ll[i] + p_dd[i]

                    p_mix = np.array(p_mix)
                    p_mix /= p_mix.sum()
                    a_mix = np.argmax(p_mix)

                    fe.close()

                    a = a_mix
                    a_his = a
                    p = p_mix
                    direction = self.walker.action_labels[a]

                # if epoch == 20:
                #     print(p)
                #     print(direction)

                memory[pos_key][direction] += 1

                if direction == '0':
                    self.walker.reset_walker_pos(self.walker.pos_x, self.walker.pos_y,
                                                 self.walker.pos_z - self.unit)
                elif direction == '45':
                    self.walker.reset_walker_pos(self.walker.pos_x + self.unit, self.walker.pos_y,
                                                 self.walker.pos_z - self.unit)
                elif direction == '90':
                    self.walker.reset_walker_pos(self.walker.pos_x + self.unit, self.walker.pos_y,
                                                 self.walker.pos_z)
                elif direction == '135':
                    self.walker.reset_walker_pos(self.walker.pos_x + self.unit, self.walker.pos_y,
                                                 self.walker.pos_z + self.unit)
                elif direction == '180':
                    self.walker.reset_walker_pos(self.walker.pos_x, self.walker.pos_y,
                                                 self.walker.pos_z + self.unit)
                elif direction == '225':
                    self.walker.reset_walker_pos(self.walker.pos_x - self.unit, self.walker.pos_y,
                                                 self.walker.pos_z + self.unit)
                elif direction == '270':
                    self.walker.reset_walker_pos(self.walker.pos_x - self.unit, self.walker.pos_y,
                                                 self.walker.pos_z)
                elif direction == '315':
                    self.walker.reset_walker_pos(self.walker.pos_x - self.unit, self.walker.pos_y,
                                                 self.walker.pos_z - self.unit)

                # fixme, don't have s_ when get source
                if self.walker.pos_x == self.src_pos_x and self.walker.pos_z == self.src_pos_z:
                    print("get source")
                    DONE = True
                    r = 5
                    s_ = np.array([0 for u in range(self.n_features)])[np.newaxis, :]

                else:
                    # fixme, rebuild reward function
                    # r = self.walker.observe_volume(self.walker.pos_x, self.walker.pos_y, self.walker.pos_z)
                    # r = 0
                    pos_key = str(self.walker.pos_x) + "*" + str(self.walker.pos_z)
                    # r /= (visit[pos_key] + 1)

                    max_angle = max(float(self.walker.action_labels[a]), float(self.walker.action_labels[a_his]))
                    min_angle = min(float(self.walker.action_labels[a]), float(self.walker.action_labels[a_his]))

                    diff = min(abs(max_angle - min_angle), 360 - max_angle + min_angle)

                    # r = 1 - abs((a + a_his) % self.n_actions - a_his) / (self.n_actions - 1)
                    r = 1 - diff / 180
                    r -= (visit[pos_key]) * 0.2

                    # todo, think about punishment
                    pub = self.detect_invalids(self.walker.pos_x, self.walker.pos_y, self.walker.pos_z)
                    if len(pub) > 0:
                        r -= 0.5

                    s_ = self.walker.observe_gcc_vector(self.walker.pos_x, self.walker.pos_y, self.walker.pos_z)
                    s_ = np.array(s_)[np.newaxis, :]

                sum_reward += r
                a_his = a

                self.walker.learn(s, a, s_, r)
                if DONE:
                    break

            # fixme, think about a new way to evaluate
            print(step)
            print(sum_reward / step)
            records_step.append(step)
            records_r.append(sum_reward / step)

            # overload now
            if epoch % 500 == 0 and epoch != 0:
                with open('save/rl_8x3x8_src_-3_1.6_-3/records_step', 'w') as f:
                    f.write(str(records_step))
                with open('save/rl_8x3x8_src_-3_1.6_-3/records_reward', 'w') as f:
                    f.write(str(records_r))

if __name__ == '__main__':
    game = Game()
    # game.detect_invalids(1, 1, 1)
    game.play()


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""GL_RadarDetections inherits GL_ObjectBuffer and provides a way to visualize detections
"""

import numpy as np

from .gl_object_buffer import GLObjectBuffer

class GLPolarGrid(GLObjectBuffer):
    vertex_shader_file: str = 'polar_grid_vertex.glsl'
    fragment_shader_file: str = 'polar_grid_fragment.glsl'

    u_linewidth: float = 0.1
    u_antialias: float = 1.0

    def __init__(self, buffer_size: int = 2000):
        super().__init__(buffer_size)

        self.load_program(self.vertex_shader_file,
                          self.fragment_shader_file)

        self.program['u_linewidth'] = self.u_linewidth
        self.program['u_antialias'] = self.u_antialias

    @property
    def type(self):
        return 'points'

    @property
    def data(self):
        if not hasattr(self, '_data'):
            self._data = \
                np.zeros(self.buffer_size, [('a_position', np.float32, 3),
                                            ('a_color', np.float32, 4),
                                            ('a_size', np.float32)])
        return self._data


from time import perf_counter
import os
import numpy as np
from typing import Union
from osgeo import gdal
from pyproj import Transformer, CRS
from pyproj.exceptions import CRSError

from vyperdatum.core import VyperCore
from vyperdatum.vypercrs import get_transformation_pipeline

class VyperRaster(VyperCore):
    """
    Using VyperCore, read from a raster and perform a vertical transformation of the data to a vdatum supported
    vertical datum CRS, optionally writing to geotiff.
    """

    def __init__(self, input_file: str = None, vdatum_directory: str = None,
                 logfile: str = None, silent: bool = False):
        super().__init__(vdatum_directory, logfile, silent)
        self.input_file = input_file
        self.input_wkt = None
        self.geotransform = None

        self.resolution_x = None
        self.resolution_y = None
        self.width = None
        self.height = None

        self.layers = []
        self.layernames = []
        self.nodatavalue = []

        self.raster_vdatum_sep = None
        self.raster_vdatum_uncertainty = None
        self.raster_vdatum_region_index = None

        self.output_geotransform = None

        if input_file:
            self.initialize()

    @property
    def is_covered(self):
        """
        Data is completely covered by the found VDatum regions

        Returns
        -------
        bool
            Returns true if data is completely covered by VDatum
        """

        if self.raster_vdatum_sep is None:
            self.log_error('No separation grid found, make sure you run get_datum_sep first', ValueError)
        return np.count_nonzero(np.isnan(self.raster_vdatum_sep)) == 0

    def initialize(self, input_file: str = None):
        """
        Get all the data we need from the input raster.  This is run automatically on instancing this class, if an input
        file is provided then.  Otherwise, use this method and provide a gdal supported file to initialize.

        Parameters
        ----------
        input_file
            file path to a gdal supported raster file
        """

        if input_file:  # can re-initialize by passing in a new file here
            self.input_file = input_file

        ofile = gdal.Open(self.input_file)
        if not ofile:
            self.log_error(f'Unable to open {self.input_file} with gdal', ValueError)

        self.log_info(f'Operating on {self.input_file}')
        self.layers = [ofile.GetRasterBand(i + 1).ReadAsArray() for i in range(ofile.RasterCount)]
        self.nodatavalue = [ofile.GetRasterBand(i + 1).GetNoDataValue() for i in range(ofile.RasterCount)]
        self.layernames = [ofile.GetRasterBand(i + 1).GetDescription() for i in range(ofile.RasterCount)]

        # readasarray doesn't seem to handle gdal nodatavalue NaN
        for lyr, ndv in zip(self.layers, self.nodatavalue):
            lyr[lyr == ndv] = np.nan

        # geotransform in this format [x origin, x pixel size, x rotation, y origin, y rotation, -y pixel size]
        self.geotransform = ofile.GetGeoTransform()
        min_x, self.resolution_x, _, max_y, _, self.resolution_y = self.geotransform
        self.width, self.height = ofile.RasterXSize, ofile.RasterYSize
        max_x = min_x + self.width * self.resolution_x
        min_y = max_y + self.height * self.resolution_y
        self.resolution_y = abs(self.resolution_y)  # store this as positive for future use

        input_crs = ofile.GetSpatialRef()
        self.input_wkt = input_crs.ExportToWkt()
        self.set_input_datum(self.input_wkt, extents = (min_x, min_y, max_x, max_y))
        ofile = None

    def _get_elevation_layer_index(self):
        """
        Find the elevation layer index

        Returns
        -------
        int
            integer index in self.layernames for the elevation layer, -1 if it does not exist
        """
        check_layer_names = [lname.lower() for lname in self.layernames]
        if 'depth' in check_layer_names:
            depth_idx = check_layer_names.index('depth')
        elif 'elevation' in check_layer_names:
            depth_idx = check_layer_names.index('elevation')
        else:
            depth_idx = None
            self.log_warning(f'Unable to find depth or elevation layer by name, layers={check_layer_names}')
        return depth_idx

    def _get_uncertainty_layer_index(self):
        """
        Find the uncertainty layer index

        Returns
        -------
        int
            integer index in self.layernames for the uncertainty layer, -1 if it does not exist
        """
        check_layer_names = [lname.lower() for lname in self.layernames]
        if 'uncertainty' in check_layer_names:
            unc_idx = check_layer_names.index('uncertainty')
        elif 'vertical uncertainty' in check_layer_names:
            unc_idx = check_layer_names.index('vertical uncertainty')
        else:
            unc_idx = None
            self.log_warning(f'Unable to find uncertainty or vertical uncertainty layer by name, layers={check_layer_names}')
        return unc_idx

    def _get_contributor_layer_index(self):
        """
        Find the contributor layer index

        Returns
        -------
        int
            integer index in self.layernames for the contributor layer, -1 if it does not exist
        """
        check_layer_names = [lname.lower() for lname in self.layernames]
        if 'contributor' in check_layer_names:
            cont_idx = check_layer_names.index('contributor')
        else:
            cont_idx = None
            self.log_warning(f'Unable to find contributor layer by name, layers={check_layer_names}')
        return cont_idx

    def get_datum_sep(self):
        """
        Build a datum separation using the VDatum regions and compound input and output
        datum definitions described in this object as attributes.  The coordinate
        reference systems (vypercrs.VyperPipelineCRS objects) must be valid for this
        process to commence.

        The datum separation, datum uncertainty, and region index corrisponding to the
        object region list are stored as object attributes.

        Raises
        ------
        ValueError
            If there are no regions in the object or the in_crs or out_crs objects are
            not valid.

        Returns
        -------
        None.

        """

        if self.regions is None:
            self.log_error('Initialization must have failed, re-initialize with a new gdal supported file', ValueError)
        if not self.in_crs.is_valid:
            self.log_error('Input datum must be valid before performing datum transformation operations.', ValueError)
        if not self.out_crs.is_valid:
            self.log_error('Output datum must be valid before performing datum transformation operations.', ValueError)
        if self.in_crs.horizontal.to_wkt() != self.out_crs.horizontal.to_wkt():
            self.log_error('Horizontal datum transformation operations for rasters are not yet supported.', NotImplementedError)

        self.pipelines = []
        self.regional_seps = []
        self.regional_uncertainties = []
        valid_counts = []
        valid_idx = []
        for region in self.regions:
            if region not in self.vdatum.regions:
                self.log_error('Region {region} not found in VDatum.', ValueError)
            # get the pipeline
            regional_sep = None
            pipeline = get_transformation_pipeline(self.in_crs, self.out_crs, region, self.vdatum.vdatum_version)
            self.pipelines.append(pipeline)
            # get each layer for for the pipeline and add to the stack
            for cmd in pipeline.split(' +step '):
                if cmd.find('vgridshift') >= 0:
                    inv = False
                    cmd_parts = cmd.split()
                    for part in cmd_parts:
                        if part == '+inv':
                            inv = True
                        elif part.startswith('grids='):
                            junk, grid_file = part.split('=')
                            grid_path = os.path.join(self.vdatum.vdatum_path, grid_file)
                    # transform, crop and resample the source grid
                    epsg = self.out_crs.horizontal.to_epsg()
                    ds = gdal.Warp('', grid_path, format = 'MEM', dstSRS = f'EPSG:{epsg}',
                                   xRes = self.resolution_x, yRes = self.resolution_y,
                                   outputBounds = [self.min_x, self.min_y, self.max_x, self.max_y])
                    band = ds.GetRasterBand(1)
                    array = band.ReadAsArray()
                    nodata = band.GetNoDataValue()
                    array[np.where(array == nodata)] = np.nan
                    if not inv:
                        array *= -1
                    ds = None
                    if regional_sep is None:
                        regional_sep = array.copy()
                    else:
                        regional_sep += array
            self.regional_seps.append(regional_sep)
            valid = np.where(~np.isnan(regional_sep))
            valid_idx.append(valid)
            valid_counts.append(len(valid[0]))
            datum_unc = self._get_output_uncertainty(region)
            regional_uncertainty = np.full(regional_sep.shape, np.nan)
            regional_uncertainty[valid] = datum_unc
            self.regional_uncertainties.append(regional_uncertainty)
        # combine the regional seps
        self.raster_vdatum_sep = np.full((self.height, self.width), np.nan)
        self.raster_vdatum_uncertainty = np.full((self.height, self.width), np.nan)
        self.raster_vdatum_region_index = np.full((self.height, self.width), np.nan)
        stack_order = np.argsort(valid_counts)
        for idx in stack_order:
            self.raster_vdatum_sep[valid_idx[idx]] = self.regional_seps[idx][valid_idx[idx]]
            self.raster_vdatum_uncertainty[valid_idx[idx]] = self.regional_uncertainties[idx][valid_idx[idx]]
            self.raster_vdatum_region_index[valid_idx[idx]] = idx

    def apply_sep(self, allow_points_outside_coverage: bool = False):
        """
        After getting the datum separation model from vdatum, use this method to apply the separation and added
        separation uncertainty.

        If allow_points_outside_coverage is True, this will pass through z values that are outside of vdatum coverage,
        but add additional uncertainty

        Parameters
        ----------
        allow_points_outside_coverage
            if True, allows through points outside of vdatum coverage

        Returns
        -------
        tuple
            tuple of layers, including elevation, uncertainty and possibly contributor
        tuple
            tuple of layer names for each layer in returned layers
        tuple
            tuple of layer nodata value for each layer in returned layers
        """

        if self.raster_vdatum_sep is None:
            self.log_error('Unable to find sep model, make sure you run get_datum_sep first', ValueError)
        elevation_layer_idx = self._get_elevation_layer_index()
        uncertainty_layer_idx = self._get_uncertainty_layer_index()
        contributor_layer_idx = self._get_contributor_layer_index()

        if elevation_layer_idx is None:
            self.log_error('Unable to find elevation layer', ValueError)
        if uncertainty_layer_idx is None:
            self.log_info('Unable to find uncertainty layer, uncertainty will be entirely based off of vdatum sep model')

        elevation_layer = self.layers[elevation_layer_idx]
        layernames = [self.layernames[elevation_layer_idx]]
        layernodata = [self.nodatavalue[elevation_layer_idx]]
        uncertainty_layer = None
        contributor_layer = None
        if uncertainty_layer_idx is not None:
            uncertainty_layer = self.layers[uncertainty_layer_idx]
            layernames.append(self.layernames[uncertainty_layer_idx])
            layernodata.append(self.nodatavalue[uncertainty_layer_idx])
            had_uncertainty = True
        else:
            layernames.append('Uncertainty')
            uncertainty_layer_idx = len(layernodata)
            layernodata.append(self.nodatavalue[elevation_layer_idx])
            had_uncertainty = False
        if contributor_layer_idx is not None:
            contributor_layer = self.layers[contributor_layer_idx]
            layernames.append(self.layernames[contributor_layer_idx])
            layernodata.append(self.nodatavalue[contributor_layer_idx])

        elev_nodata = np.isnan(elevation_layer)
        elev_nodata_idx = np.where(elev_nodata)
        missing = np.isnan(self.raster_vdatum_sep)
        missing_idx = np.where(missing & ~elev_nodata)
        missing_count = len(missing_idx[0])
        self.log_info(f'Applying vdatum separation model to {self.raster_vdatum_sep.size} total points')

        if self.in_crs.is_height == self.out_crs.is_height:
            flip = 1
        else:
            flip = -1

        if self.in_crs.is_height == True:
            final_elevation_layer = flip * (elevation_layer + self.raster_vdatum_sep)
        else:
            final_elevation_layer = flip * (elevation_layer - self.raster_vdatum_sep)
        final_elevation_layer[elev_nodata_idx] = layernodata[elevation_layer_idx]

        if had_uncertainty:
            final_uncertainty_layer = uncertainty_layer + self.raster_vdatum_uncertainty
        else:
            final_uncertainty_layer = self.raster_vdatum_uncertainty

        final_uncertainty_layer[elev_nodata_idx] = layernodata[uncertainty_layer_idx]

        if contributor_layer is not None:
            contributor_layer[elev_nodata_idx] = layernodata[contributor_layer_idx]

        if missing_count > 0:
            if allow_points_outside_coverage:
                self.log_info(f'Allowing {missing_count} points that are outside of vdatum coverage.')
                final_elevation_layer[missing_idx] = flip * elevation_layer[missing_idx]
                if self.in_crs.is_height:
                    z_values = elevation_layer[missing_idx]
                else:
                    z_values = -elevation_layer[missing_idx]
                u_values = 3 - 0.06 * z_values
                u_values[np.where(z_values > 0)] = 3.0
                if had_uncertainty:
                    keep_uncert_sub_idx = np.where(u_values < uncertainty_layer[missing_idx])[0]
                    if len(keep_uncert_sub_idx) > 0:
                        self.log_info(f'Maintaining {len(keep_uncert_sub_idx)} points from source uncertainty since greater than CATZOC D vertical uncertainty.')
                        u_values[keep_uncert_sub_idx] = uncertainty_layer[missing_idx[0][keep_uncert_sub_idx], missing_idx[1][keep_uncert_sub_idx]]
                final_uncertainty_layer[missing_idx] = u_values
            else:
                self.log_info(f'applying nodatavalue to {missing_count} points that are outside of vdatum coverage')
                final_elevation_layer[missing_idx] = layernodata[elevation_layer_idx]
                final_uncertainty_layer[missing_idx] = layernodata[uncertainty_layer_idx]
                if contributor_layer is not None:
                    contributor_layer[missing_idx] = layernodata[contributor_layer_idx]

        layers = (final_elevation_layer, final_uncertainty_layer, contributor_layer)
        return layers, layernames, layernodata

    def transform_raster(self, output_datum: Union[str, int, tuple], input_datum: Union[int, str, tuple] = None,
                         allow_points_outside_coverage: bool = False, output_filename: str = None):
        """
        Main method of this class, contains all the other methods and allows you to transform the source raster to a
        different vertical datum using VDatum.

        Parameters
        ----------
        output_datum
            inputs to vypercrs VyperPipelineCRS
        input_datum
            inputs to vypercrs VyperPipelineCRS
        allow_points_outside_coverage
            if True, allows through points outside of vdatum coverage
        output_filename
            if provided, writes the new raster to geotiff

        Returns
        -------
        tuple
            tuple of layers, including elevation, uncertainty and possibly contributor
        tuple
            tuple of layer names for each layer in returned layers
        tuple
            tuple of layer nodata value for each layer in returned layers
        """

        if input_datum:
            self.set_input_datum(input_datum)

        if output_datum:
            self.set_output_datum(output_datum)

        if self.regions is None:
            self.log_error(f'Unable to find regions for raster using ({self.geographic_min_x},{self.geographic_min_y}), '
                           f'({self.geographic_max_x},{self.geographic_max_y})', ValueError)

        start_cnt = perf_counter()
        self.log_info(f'Begin work on {os.path.basename(self.input_file)}')
        self.get_datum_sep()
        layers, layernames, layernodata = self.apply_sep(allow_points_outside_coverage=allow_points_outside_coverage)
        if output_filename:
            if layers[2] is not None:  # contributor
                tiffdata = np.concatenate([layers[0][None, :, :], layers[1][None, :, :], layers[2][None, :, :]])
            else:
                tiffdata = np.concatenate([layers[0][None, :, :], layers[1][None, :, :]])
            tiffdata = np.round(tiffdata, 3)
            self._write_gdal_geotiff(output_filename, tiffdata, layernames, layernodata)
        end_cnt = perf_counter()
        self.log_info(f'Raster transformation complete: Elapsed time {end_cnt - start_cnt} seconds')
        return layers, layernames, layernodata

    def _custom_output_crs(self, destination_epsg: int):
        try:
            out_crs = CRS.from_epsg(int(destination_epsg))
        except CRSError:
            self.log_error(f'Expected integer epsg code that is readable by the pyproj CRS object, got {destination_epsg}',
                           ValueError)
        if out_crs.is_vertical:
            self.log_error(f'Only 2d coordinate system epsg supported when using the new_2d_crs option, got {destination_epsg}',
                           ValueError)
        in_crs = CRS.from_epsg(6319)
        # Transformer.transform input order is based on the CRS, see CRS.geodetic_crs.axis_info
        # - lon, lat - this appears to be valid when using CRS from proj4 string
        # - lat, lon - this appears to be valid when using CRS from epsg
        # use the always_xy option to force the transform to expect lon/lat order
        transformer = Transformer.from_crs(in_crs, out_crs, always_xy=True)
        new_min_x, new_min_y = transformer.transform(self.geographic_min_x, self.geographic_min_y)
        new_max_x, new_max_y = transformer.transform(self.geographic_max_x, self.geographic_max_y)

        # if out_crs.is_projected:
        #     if new_min_x < 0:
        #         new_min_x = int(np.ceil(new_min_x))
        #     else:
        #         new_min_x = int(np.floor(new_min_x))
        #     if new_min_y < 0:
        #         new_min_y = int(np.ceil(new_min_y))
        #     else:
        #         new_min_y = int(np.floor(new_min_y))
        #     if new_max_x < 0:
        #         new_max_x = int(np.floor(new_max_x))
        #     else:
        #         new_max_x = int(np.ceil(new_max_x))
        #     if new_max_y < 0:
        #         new_max_y = int(np.floor(new_max_y))
        #     else:
        #         new_max_y = int(np.ceil(new_max_y))

        x_rez = (new_max_x - new_min_x) / self.width
        y_rez = (new_max_y - new_min_y) / self.height
        self.output_geotransform = (new_min_x, x_rez, 0.0, new_max_y, 0.0, -y_rez)
        self.out_crs.horiz_wkt = out_crs.to_wkt()

    def _write_gdal_geotiff(self, outfile: str, data: tuple, band_names: tuple, nodatavalue: tuple,
                            use_custom_geotransform: bool = False):
        """
        Build a geotiff from the transformed raster data

        Parameters
        ----------
        outfile
            output file that we write the raster data to
        data
            arrays for each layer that we want to write to file
        band_names
            names for each layer that we want to write to file
        nodatavalue
            nodatavalues for each layer that we want to write to file
        use_custom_geotransform
            if True, rely on the custom output geotransform we made from the provided 2d epsg in transform raster
        """

        numlyrs, rows, cols = data.shape
        driver = gdal.GetDriverByName('GTiff')
        out_raster = driver.Create(outfile, cols, rows, numlyrs, gdal.GDT_Float32)
        if use_custom_geotransform:
            out_raster.SetGeoTransform(self.output_geotransform)
        else:
            out_raster.SetGeoTransform(self.geotransform)
        for lyr_num in range(numlyrs):
            outband = out_raster.GetRasterBand(lyr_num + 1)
            outband.SetNoDataValue(nodatavalue[lyr_num])
            outband.SetDescription(band_names[lyr_num])
            outband.WriteArray(data[lyr_num])
            outband = None
        out_raster.SetProjection(self.out_crs.to_wkt())
        out_raster = None


######################################################################

# NotesToType.py  :  a script which turns notes detected by the
# microphone into keyboard output.

# Dependencies: numpy, pyaudio, pyautogui, audioop

# The default configuration for notes are:

# C4 -> COPY (CTRL + C)
# D4 -> PASTE (CTRL + V)
# E4 -> CUT (CTRL + X)
# F4 -> SeARCH ON PAGE (CTRL + F)
# G4 -> UNDO (CTRL + Z)
# A4 -> REDO (CTRL + Y)
# B4 -> SAVE (CTRL + S)
# C5 -> OPEN (CTRL + O)

# These functions can be changed to automate different keyboard
# or mouse outputs supported in pyautogui. The note range can
# also be edited. Pure tones work best for accuracy, but instruments
# and voice can also be used.

# This script was built on top of tuner.py, a ukelele tuner
# written by Matt Zucker in 2016. On top of this framework,
# I added decibel detection, keyboard atuomation, and other
# optimizations to make it better suited for converting notes
# into keyboard output.

# Feel free to reuse, edit, or transform this script into
# whatever you like!

# Written by Justin Lin in July 2020

######################################################################

######################################################################
# tuner.py - a minimal command-line guitar/ukulele tuner in Python.
# Requires numpy and pyaudio.
######################################################################
# Author:  Matt Zucker
# Date:    July 2016
# License: Creative Commons Attribution-ShareAlike 3.0
#          https://creativecommons.org/licenses/by-sa/3.0/us/
######################################################################

import numpy as np
import pyaudio
from time import sleep
import pyautogui
import audioop
import math

# Non-repeat variables: used to prevent notes from unintentionally repeating key presses
c4 = False
d4 = False
e4 = False
f4 = False
g4 = False
a4 = False
b4 = False
c5 = False

# Non-repeat timers, used in timing for this same purpose
inputDelayTimer = 0
nonRepeatTimer = 0

# These constants determine how long the above timers must run in order to finish
INPUT_DELAY_LIMIT = 8
NON_REPEAT_DELAY = 7

# Decibel limit, used to control what volume notes must reach to trigger output
DECIBEL_LIMIT = 35

# Freq variable, used to store the frequency of input from the microphone
freq = 1

######################################################################
# Feel free to play with these numbers. Might want to change NOTE_MIN
# and NOTE_MAX especially for guitar/bass. Probably want to keep
# FRAME_SIZE and FRAMES_PER_FFT to be powers of two.

# Note: NOTE_MIN AND NOTE_MAX determine the lowest and highest notes the
# tuner can detect. Example: if you lower NOTE_MIN by 1, the lowest detectable
# note goes down by one semitone in most cases. If you increase one of those variables, vice-versa.

NOTE_MIN = 61       # C4
NOTE_MAX = 71       # C5
FSAMP = 22050       # Sampling frequency in Hz
FRAME_SIZE = 2048   # How many samples per frame?
FRAMES_PER_FFT = 16 # FFT takes average across how many frames?

######################################################################
# Derived quantities from constants above. Note that as
# SAMPLES_PER_FFT goes up, the frequency step size decreases (so
# resolution increases); however, it will incur more delay to process
# new sounds.

SAMPLES_PER_FFT = FRAME_SIZE*FRAMES_PER_FFT
FREQ_STEP = float(FSAMP)/SAMPLES_PER_FFT

######################################################################
# Note names for printing out notes

NOTE_NAMES = 'C C# D D# E F F# G G# A A# B'.split()

######################################################################
# These three functions are based upon this very useful webpage:
# https://newt.phys.unsw.edu.au/jw/notes.html

def freq_to_number(f): return 69 + 12*np.log2(f/440.0)
def number_to_freq(n): return 440 * 2.0**((n-69)/12.0)
def note_name(n): return NOTE_NAMES[n % 12] + str(n/12 - 1)

######################################################################
# Ok, ready to go now.

# Get min/max index within FFT of notes we care about.
# See docs for numpy.rfftfreq()
def note_to_fftbin(n): return number_to_freq(n)/FREQ_STEP
imin = max(0, int(np.floor(note_to_fftbin(NOTE_MIN-1))))
imax = min(SAMPLES_PER_FFT, int(np.ceil(note_to_fftbin(NOTE_MAX+1))))

# Allocate space to run an FFT.
buf = np.zeros(SAMPLES_PER_FFT, dtype=np.float32)
num_frames = 0

# Initialize audio
stream = pyaudio.PyAudio().open(format=pyaudio.paInt16,
                                channels=1,
                                rate=FSAMP,
                                input=True,
                                frames_per_buffer=FRAME_SIZE)

stream.start_stream()

# Create Hanning window function
window = 0.5 * (1 - np.cos(np.linspace(0, 2*np.pi, SAMPLES_PER_FFT, False)))

# Print initial text
print ("Starting NotesToType...")

sleep(3)

print('\n\nBeginning sampling at', FSAMP, 'Hz with max resolution of', FREQ_STEP, 'Hz')
print()

sleep(1.5)

# As long as we are getting data:
while stream.is_active:

    # Shift the buffer down and new data in
    buf[:-FRAME_SIZE] = buf[FRAME_SIZE:]
    buf[-FRAME_SIZE:] = np.frombuffer(stream.read(FRAME_SIZE), np.int16)

    # Run the FFT on the windowed buffer
    fft = np.fft.rfft(buf * window)

    # Decibel and RMS variables: used to determine if sound is loud enough
    data = stream.read(FRAME_SIZE)
    rms = audioop.rms(data,2) + 1                   # The '+ 1' is to prevent any log errors when taking the log of 0, but may reduce accuracy

    # Equation to obtain volume of noise from mic in decibels
    decibel = 20 * math.log10(rms)

    ### Get frequency of maximum response in range ###

    # If the noise from mic is loud enough...
    if decibel > DECIBEL_LIMIT:

        # Start adding to this timer. This creates a small delay to increase the accuracy of readings.
        inputDelayTimer += 1

        # Once the timer is up...
        if inputDelayTimer == INPUT_DELAY_LIMIT:

            # Take the frequency of the input from the mic
            freq = (np.abs(fft[imin:imax]).argmax() + imin) * FREQ_STEP
            inputDelayTimer = 0

    # If the input from the mic is not loud enough...
    else:

        # Set freq to 1 to prevent accidental outputs
        freq = 1

    # Get note number and nearest note
    n = freq_to_number(freq)
    n0 = int(round(n))

    # Console output once we have a full buffer
    num_frames += 1

    # If freq is being taken from the mic, print to the console the detected freq, note, number, cents, etc.
    if freq != 1:
        if num_frames >= FRAMES_PER_FFT:
            print('freq: {:7.2f} Hz     note: {:>3s} {:+.2f}'.format(
                freq, note_name(n0), n-n0))

    # If freq is not being taken from the mic
    else:

        print("No note detected.")

    ### TURN DETECTED NOTES INTO KEYBOARD OUTPUT ###
    if decibel > DECIBEL_LIMIT:

        # C4 -> COPY
        if 253 <= freq <= 270 and decibel > DECIBEL_LIMIT and c4 == False:

            pyautogui.hotkey("ctrl", "c")

        # D4 -> PASTE
        elif 290 <= freq <= 300 and decibel > DECIBEL_LIMIT and d4 == False:

            pyautogui.hotkey("ctrl", "v")
            d4 = True
            sleep (1)

        # E4 -> CUT
        elif 320 <= freq <= 335 and decibel > DECIBEL_LIMIT and e4 == False:

            pyautogui.hotkey("ctrl", "x")
            e4 = True

        # F4 -> SeARCH ON PAGE (CTRL + F)
        elif 345 <= freq <= 360 and decibel > DECIBEL_LIMIT and f4 == False:

            pyautogui.hotkey("ctrl", "f")
            f4 = True

        # G4 -> UNDO
        elif 380 <= freq <= 405 and decibel > DECIBEL_LIMIT and g4 == False:

            pyautogui.hotkey("ctrl", "z")
            g4 = True

        # A4 -> REDO
        elif 431 <= freq <= 450 and decibel > DECIBEL_LIMIT and a4 == False:

            pyautogui.hotkey("ctrl", "y")
            a4 = True

        # B4 -> SAVE
        elif 480 <= freq <= 500 and decibel > DECIBEL_LIMIT and b4 == False:

            pyautogui.hotkey("ctrl", "s")
            b4 = True

        # C5 -> OPEN
        elif 515 <= freq <= 535 and decibel > DECIBEL_LIMIT and c5 == False:

            pyautogui.hotkey("ctrl", "o")
            c5 = True

    #################################################

    # Add one to this timer every time data from the microphone is taken and printed
    if decibel > DECIBEL_LIMIT:

        nonRepeatTimer += 1

    # This if statement helps prevent key output from repeating from one note input
    if nonRepeatTimer == NON_REPEAT_DELAY:

        # Allow notes to trigger output again
        c4 = False
        d4 = False
        e4 = False
        f4 = False
        g4 = False
        a4 = False
        b4 = False
        c5 = False

        # Reset this timer
        nonRepeatTimer = 0


import detectron2
from detectron2.utils.logger import setup_logger
setup_logger()

# import some common libraries
import numpy as np
import cv2
import random

# import some common detectron2 utilities
from detectron2 import model_zoo
from detectron2.engine import DefaultPredictor
from detectron2.config import get_cfg
from detectron2.utils.visualizer import Visualizer
from detectron2.data import MetadataCatalog
from detectron2.data import DatasetCatalog, MetadataCatalog, build_detection_test_loader, build_detection_train_loader
import os
import json
from detectron2.structures import BoxMode

# radiate sdk
import sys
sys.path.insert(0, '..')
import radiate

root_dir = '../data/radiate/'

def gen_boundingbox(bbox, angle):
        theta = np.deg2rad(-angle)
        R = np.array([[np.cos(theta), -np.sin(theta)],
                      [np.sin(theta), np.cos(theta)]])
        points = np.array([[bbox[0], bbox[1]],
                           [bbox[0] + bbox[2], bbox[1]],
                           [bbox[0] + bbox[2], bbox[1] + bbox[3]],
                           [bbox[0], bbox[1] + bbox[3]]]).T

        cx = bbox[0] + bbox[2] / 2
        cy = bbox[1] + bbox[3] / 2
        T = np.array([[cx], [cy]])

        points = points - T
        points = np.matmul(R, points) + T
        points = points.astype(int)

        min_x = np.min(points[0, :])
        min_y = np.min(points[1, :])
        max_x = np.max(points[0, :])
        max_y = np.max(points[1, :])

        return min_x, min_y, max_x, max_y

def get_radar_dicts(folders):
        dataset_dicts = []
        idd = 0
        folder_size = len(folders)
        for folder in folders:
            radar_folder = os.path.join(root_dir, folder,'reconstruct-same-meas-30')
            annotation_path = os.path.join(root_dir,
                                           folder, 'annotations', 'annotations.json')
            with open(annotation_path, 'r') as f_annotation:
                annotation = json.load(f_annotation)

            radar_files = os.listdir(radar_folder)
            radar_files.sort()
            for frame_number in range(len(radar_files)):
                record = {}
                objs = []
                bb_created = False
                idd += 1
                filename = os.path.join(
                    radar_folder, radar_files[frame_number])

                if (not os.path.isfile(filename)):
                    print(filename)
                    continue
                record["file_name"] = filename
                record["image_id"] = idd
                record["height"] = 1152
                record["width"] = 1152

                for object in annotation:
                    if (object['bboxes'][frame_number]):
                        class_obj = object['class_name']
                        if (class_obj != 'pedestrian' and class_obj != 'group_of_pedestrians'):
                            bbox = object['bboxes'][frame_number]['position']
                            angle = object['bboxes'][frame_number]['rotation']
                            bb_created = True
                            if cfg.MODEL.PROPOSAL_GENERATOR.NAME == "RRPN":
                                cx = bbox[0] + bbox[2] / 2
                                cy = bbox[1] + bbox[3] / 2
                                wid = bbox[2]
                                hei = bbox[3]
                                obj = {
                                    "bbox": [cx, cy, wid, hei, angle],
                                    "bbox_mode": BoxMode.XYWHA_ABS,
                                    "category_id": 0,
                                    "iscrowd": 0
                                }
                            else:
                                xmin, ymin, xmax, ymax = gen_boundingbox(
                                    bbox, angle)
                                #xmin, ymin, xmax, ymax = bbox[0],bbox[1],bbox[2]-bbox[0],bbox[3]-bbox[0]
                                obj = {
                                    "bbox": [xmin, ymin, xmax, ymax],
                                    "bbox_mode": BoxMode.XYXY_ABS,
                                    "category_id": 0,
                                    "iscrowd": 0
                                }

                            objs.append(obj)
                if bb_created:
                    record["annotations"] = objs
                    dataset_dicts.append(record)
        return dataset_dicts

network = 'faster_rcnn_R_50_FPN_3x'
setting = 'good_and_bad_weather_radar'

# time (s) to retrieve next frame
dt = 0.25

cfg = get_cfg()
# add project-specific config (e.g., TensorMask) here if you're not running a model in detectron2's core library
cfg.merge_from_file(os.path.join('test','config' , network + '.yaml'))
#cfg.MODEL.DEVICE = 'cpu'
cfg.MODEL.WEIGHTS = os.path.join('weights',  network +'_' + setting + '.pth')
#cfg.MODEL.WEIGHTS = 'train_results/faster_rcnn_R_50_FPN_3x_good_and_bad_weather/model_final.pth'
cfg.MODEL.ROI_HEADS.NUM_CLASSES = 1  # only has one class (vehicle)
cfg.MODEL.ROI_HEADS.NMS_THRESH_TEST = 0.2
cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.5
cfg.MODEL.ANCHOR_GENERATOR.SIZES = [[8, 16, 32, 64, 128]]
predictor = DefaultPredictor(cfg)

root_dir = '../data/radiate/'
folders_test = []
for curr_dir in os.listdir(root_dir):
    with open(os.path.join(root_dir, curr_dir, 'meta.json')) as f:
        meta = json.load(f)
    if meta["set"] == "test":
        folders_test.append(curr_dir)
folders_test=['night_1_4'] #'tiny_foggy' #, 'snow_1_0' #, 'night_1_4', 'motorway_2_2']
print("test folders:", folders_test)
dataset_test_name = 'test'
DatasetCatalog.register(dataset_test_name,
                            lambda: get_radar_dicts(folders_test))
MetadataCatalog.get(dataset_test_name).set(thing_classes=["vehicle"])
dataset_test_name = 'test'
from detectron2.evaluation import COCOEvaluator, inference_on_dataset
from detectron2.data import build_detection_test_loader
evaluator = COCOEvaluator(dataset_test_name,cfg,False, output_dir="./output")
val_loader = build_detection_test_loader(cfg, dataset_test_name)
print(inference_on_dataset(predictor.model, val_loader, evaluator))


#!/bin/env python3
# -*- coding: utf-8 -*-

import numpy as np
import tensorflow as tf

from yolo_v3 import yolo_v3, load_weights, detections_boxes, non_max_suppression

FLAGS = tf.app.flags.FLAGS

tf.app.flags.DEFINE_string('class_names', 'coco.names', 'File with class names')
tf.app.flags.DEFINE_string('weights_file', 'yolov3.weights', 'Binary file with detector weights')
tf.app.flags.DEFINE_string('output_file', '', 'Protobuf file with frozen weights and graph')

tf.app.flags.DEFINE_integer('size', 448, 'Image size')
tf.app.flags.DEFINE_bool('use_xla', False, 'Runtime optimization')

def load_coco_names(file_name):
    names = {}
    with open(file_name) as f:
        for id, name in enumerate(f):
            names[id] = name
    return names

def main(argv=None):
    # placeholder for detector inputs
    classes = load_coco_names(FLAGS.class_names)
    inputs = tf.placeholder(tf.float32, [None, None, None, 3], name="input")
    resize_method = tf.image.ResizeMethod.BILINEAR
    inputs_resized = tf.image.resize_images(
        images=inputs, size=[FLAGS.size, FLAGS.size], method=resize_method, align_corners=True)

    with tf.variable_scope('detector'):
        # format = 'NCHW'
        format = 'NHWC'
        detections = yolo_v3(inputs_resized, len(classes), data_format=format)
        load_ops = load_weights(tf.global_variables(scope='detector'), FLAGS.weights_file)

    boxes = detections_boxes(detections, FLAGS.size, FLAGS.size)
    boxes = tf.identity(boxes, "output")

    config = tf.ConfigProto(device_count={'GPU': 1})
    config.gpu_options.allow_growth = True
    config.gpu_options.per_process_gpu_memory_fraction = 0.1
    if FLAGS.use_xla:
        jit_level = tf.OptimizerOptions.ON_1
        config.graph_options.optimizer_options.global_jit_level = jit_level

    with tf.Session(config=config) as sess:
        sess.run(tf.global_variables_initializer())
        sess.run(tf.local_variables_initializer())
        sess.run(load_ops)

        print("Finished loading graph")
        print("%d ops in the base graph." % len(sess.graph.get_operations()))

        # save the (trained) model
        # We use a built-in TF helper to export variables to constants
        output_node_names = 'output'
        output_graph_def = tf.graph_util.convert_variables_to_constants(
            sess,  # The session is used to retrieve the weights
            tf.get_default_graph().as_graph_def(),  # The graph_def is used to retrieve the nodes
            output_node_names.split(",")  # The output node names are used to select the usefull nodes
        )

        tf.train.write_graph(output_graph_def, '', FLAGS.output_file, False)
        print("%d ops in the final graph." % len(output_graph_def.node))

if __name__ == '__main__':
    tf.app.run()