# Importation des bibliothèques nécessaires
import numpy as np
from keras.models import load_model
import os
import librosa
import soundfile as sf
from pydub import AudioSegment
from datetime import datetime
from flask import Flask, request, jsonify, make_response
import noisereduce as nr
import tensorflow as tf
import matplotlib.pyplot as plt
from flaskext.mysql import MySQL

# Définition du répertoire contenant les données
directory_path = "Data/DATASET_NEW"

# Récupérer les noms des dossiers dans le répertoire donné
def get_folder_names(directory):
    folder_names = []
    for item in os.listdir(directory):
        if os.path.isdir(os.path.join(directory, item)):
            folder_names.append(item)
    return folder_names

# Obtenir la date et l'heure actuelles sous forme de chaîne de caractères
def get_datetime():
    now = datetime.now()

    year = str(now.year)
    month = str(now.month).zfill(2)
    day = str(now.day).zfill(2)
    hours = str(now.hour).zfill(2)
    minutes = str(now.minute).zfill(2)
    seconds = str(now.second).zfill(2)

    datetime_string = f"_{year}_{month}_{day}_{hours}_{minutes}_{seconds}"

    return datetime_string

def denoise_audio(input_file, output_file, reduction_strength=0.5):
    # Charger l'enregistrement audio
    audio, sr = sf.read(input_file)

    # Réduction du bruit
    denoised_audio = nr.reduce_noise(audio, sr, reduction_strength)

    # Sauvegarder le fichier audio débruité
    os.remove(input_file)
    sf.write(output_file, denoised_audio, sr)

    print("Enregistrement débruité avec succès!")

def cut_audio(file_path, output_file_path, duration):
    # Charger l'enregistrement audio
    audio, sr = sf.read(file_path)

    # Calculer le nombre d'échantillons à conserver
    samples_to_keep = int(sr * duration)

    # Couper les derniers échantillons
    cut_audio = audio[samples_to_keep:]

    # Sauvegarder le fichier audio coupé
    os.remove(file_path)
    sf.write(output_file_path, cut_audio, sr)

    print("Enregistrement coupé avec succès!")

#Tracer le graphe d'un audio
def plot_audio_waveform(file_path):
    # Charger l'enregistrement audio
    audio, sr = librosa.load(file_path)

    # Afficher la forme d'onde
    plt.figure(figsize=(12, 4))
    librosa.display.waveshow(audio, sr=sr)
    plt.title('Forme d\'onde audio')
    plt.xlabel('Temps (s)')
    plt.ylabel('Amplitude')
    plt.show()

#Donnée regroupré pour predire la commande suivante
donnees = {
    'etat1': [['active', 'allume', 'arrêt', 'désactiver', 'etat', 'éteint'], ['camera', 'lecteur', 'lecteur', 'télévision', 'lumière', 'climatisation', 'ventilateur', 'cuisinière', 'cuisine', 'gazinière', 'réfrigérateur', 'radio'], ['Salon', 'terrasse', 'balcon', 'chambre']],
    'etat2': [['augmente', 'diminue'], ['volume', 'vitesse', 'chauffage', 'température']],
    'etat3': [['ouvre', 'ferme'], ['rideaux', 'fenêtre', 'porte', 'portail']],
    'etat4': [['passe', 'précédent', 'suivant', 'change'], ['chaîne', 'musique', 'piste', 'radio']],
    'etat5': [['chaîne'], ['radio', 'télévision'], ['passe', 'précédent', 'suivant', 'change']],
}
#Fonction de prediction du choix suivant
def verifier_choix(choix, etape=1):
    res_correspondante = []
    for suivant, reponses in donnees.items():
        if choix in reponses[etape - 1]:
            res_correspondante = reponses[etape]
            return res_correspondante
    return res_correspondante

current_datetime_string = get_datetime()

# Appel de la fonction pour récupérer les noms des dossiers
nameList = get_folder_names(directory_path)

# crée une instance de l'application Flask
app = Flask(__name__)

# Configuration de la base de données MySQL
app.config['MYSQL_DATABASE_USER'] = 'root'
app.config['MYSQL_DATABASE_PASSWORD'] = ''
app.config['MYSQL_DATABASE_DB'] = 'intellihouse'
app.config['MYSQL_DATABASE_HOST'] = 'localhost'

mysql = MySQL(app)

# Configuration du répertoire de sauvegarde des fichiers audios recus
app.config['UPLOAD_FOLDER'] = 'file'

# Charger le modèle entraîné
model = load_model('Model/model8.h5')

@app.route('/api/<api_key>/predict', methods=['POST'])
def predict(api_key):
    # Verification de la clé "api_key"

    cursor = mysql.get_db().cursor()
    cursor.execute('SELECT id, nbr_commande FROM users WHERE token = %s', (api_key,))
    data = cursor.fetchall()
    id = 0
    for row in data:
        id = row[0]
        nbr_commande = int(row[1])

    print(id, nbr_commande)

    #Renvoie un status false si la cle ne correspond pas a celui d'un utilisateur de la base de données
    if id == 0 :
        response = {
                'status': False,
                'mots': "",
                'proposition': [],
                'Description': "Clé de l'API Invalide"
            }
        response = make_response(response)
        response.headers.add('Access-Control-Allow-Origin', '*')

        return response

    # Mise a jour du compteur de comande vocal

    nbr_commande += 1
    cursor.execute('UPDATE users SET nbr_commande = %s WHERE id = %s', (nbr_commande, int(id),))

    # Vérifier si un fichier audio a été envoyé dans la requête

    if 'audio' not in request.files:
        return jsonify(error='Aucun fichier audio n\'a été envoyé.')

    audio_file = request.files['audio']

    # Sauvegarder le fichier audio dans le répertoire de sauvegarde
    filename = audio_file.filename
    save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    audio_file

    # Convertir le fichier audio en WAV
    wav_path = os.path.join(app.config['UPLOAD_FOLDER'], 'audio'+get_datetime()+'.wav')
    audio = AudioSegment.from_file(save_path)
    audio = audio.set_sample_width(2)  # 2 bytes = 16 bits
    audio.export(wav_path, format='wav')
    os.remove(save_path)

    denoise_audio(wav_path,wav_path)
    cut_audio(wav_path,wav_path, duration=0.1)

    # Extraire les caractéristiques audio
    data, sampling_rate = librosa.load(wav_path, res_type='kaiser_fast')
    mfccs = np.mean(librosa.feature.mfcc(y=data, sr=sampling_rate, n_mfcc=40).T, axis=0)
    X = np.array([mfccs])

    # prédiction sur l'audio

    predictions = model.predict(X)

    # Obtenir l'index de la classe prédite
    predicted_class = np.argmax(predictions)

    # Renvoyer la prédiction sous forme de réponse JSON

    response = {
                'status': True,
                'mots': nameList[predicted_class],
                'proposition': verifier_choix(nameList[predicted_class])
            }

    response = make_response(response)
    response.headers.add('Access-Control-Allow-Origin', '*')

    return response

if __name__ == '__main__':
    app.run(debug=True)


import torch
import torch.nn as nn

from .cutmix import CutMixer

class DenseCL(nn.Module):
    """
    Build a DenseCL model with: a query encoder, a key encoder, and a queue
    https://arxiv.org/abs/2011.09157
    """
    def __init__(self, base_model, dim=128, K=65536, m=0.999, T=0.07, mlp=False, cutmix=False):
        """
        dim: feature dimension (default: 128)
        K: queue size; number of negative keys (default: 65536)
        m: moco momentum of updating key encoder (default: 0.999)
        T: softmax temperature (default: 0.07)
        """
        super(DenseCL, self).__init__()

        self.cutmixer = CutMixer(T=T) if cutmix else None

        self.K = K
        self.m = m
        self.T = T

        # create the encoders
        # num_classes is the output fc dimension

        self.encoder_q = base_model(num_classes=dim)
        self.encoder_k = base_model(num_classes=dim)

        if mlp:  # hack: brute-force replacement
            dim_mlp = self.encoder_q.fc.weight.shape[1]
            self.encoder_q.fc = nn.Sequential(nn.Linear(dim_mlp, dim_mlp), nn.ReLU(), self.encoder_q.fc)
            self.encoder_k.fc = nn.Sequential(nn.Linear(dim_mlp, dim_mlp), nn.ReLU(), self.encoder_k.fc)

        for param_q, param_k in zip(self.encoder_q.parameters(), self.encoder_k.parameters()):
            param_k.data.copy_(param_q.data)  # initialize
            param_k.requires_grad = False  # not update by gradient

        # create the queue
        self.register_buffer("queue", torch.randn(dim, K))
        self.register_buffer("queue_dense", torch.randn(dim, K))
        self.queue = nn.functional.normalize(self.queue, dim=0)
        self.queue_dense = nn.functional.normalize(self.queue_dense, dim=0)

        self.register_buffer("queue_ptr", torch.zeros(1, dtype=torch.long))

        self.crit_cls = nn.CrossEntropyLoss()
        self.crit_dense = nn.CrossEntropyLoss()

    @torch.no_grad()
    def _momentum_update_key_encoder(self):
        """
        Momentum update of the key encoder
        """
        for param_q, param_k in zip(self.encoder_q.parameters(), self.encoder_k.parameters()):
            param_k.data = param_k.data * self.m + param_q.data * (1. - self.m)

    @torch.no_grad()
    def _dequeue_and_enqueue(self, keys, dense_keys):
        # gather keys before updating queue
        keys = concat_all_gather(keys)
        dense_keys = nn.functional.normalize(dense_keys.mean(dim=2), dim=1)
        dense_keys = concat_all_gather(dense_keys)

        batch_size = keys.shape[0]

        ptr = int(self.queue_ptr)
        assert self.K % batch_size == 0  # for simplicity

        # replace the keys at ptr (dequeue and enqueue)
        self.queue[:, ptr:ptr + batch_size] = keys.T
        self.queue_dense[:, ptr:ptr + batch_size] = dense_keys.T
        ptr = (ptr + batch_size) % self.K  # move pointer

        self.queue_ptr[0] = ptr

    @torch.no_grad()
    def _batch_shuffle_ddp(self, x):
        """
        Batch shuffle, for making use of BatchNorm.
        *** Only support DistributedDataParallel (DDP) model. ***
        """
        # gather from all gpus
        batch_size_this = x.shape[0]
        x_gather = concat_all_gather(x)
        batch_size_all = x_gather.shape[0]

        num_gpus = batch_size_all // batch_size_this

        # random shuffle index
        idx_shuffle = torch.randperm(batch_size_all).cuda()

        # broadcast to all gpus
        torch.distributed.broadcast(idx_shuffle, src=0)

        # index for restoring
        idx_unshuffle = torch.argsort(idx_shuffle)

        # shuffled index for this gpu
        gpu_idx = torch.distributed.get_rank()
        idx_this = idx_shuffle.view(num_gpus, -1)[gpu_idx]

        return x_gather[idx_this], idx_unshuffle

    @torch.no_grad()
    def _batch_unshuffle_ddp(self, x, feat_k, dense_k_norm, idx_unshuffle):
        """
        Undo batch shuffle.
        *** Only support DistributedDataParallel (DDP) model. ***
        """
        # gather from all gpus
        batch_size_this = x.shape[0]
        x_gather = concat_all_gather(x)
        feat_k_gather = concat_all_gather(feat_k)
        dense_k_norm_gather = concat_all_gather(dense_k_norm)
        batch_size_all = x_gather.shape[0]

        num_gpus = batch_size_all // batch_size_this

        # restored index for this gpu
        gpu_idx = torch.distributed.get_rank()
        idx_this = idx_unshuffle.view(num_gpus, -1)[gpu_idx]

        return x_gather[idx_this], feat_k_gather[idx_this], dense_k_norm_gather[idx_this]

    def forward(self, im_q, im_k):
        """
        Input:
            im_q: a batch of query images
            im_k: a batch of key images
        Output:
            logits, targets
        """

        # compute query features
        q, dense_q, feat_q = self.encoder_q(im_q)  # queries: NxC
        q = nn.functional.normalize(q, dim=1)
        n, c, h, w = feat_q.size()
        dim_dense = dense_q.size(1)
        dense_q, feat_q = dense_q.view(n, dim_dense, -1), feat_q.view(n, c, -1)
        dense_q = nn.functional.normalize(dense_q, dim=1)

        # compute key features
        with torch.no_grad():  # no gradient to keys
            self._momentum_update_key_encoder()  # update the key encoder

            # shuffle for making use of BN
            im_k, idx_unshuffle = self._batch_shuffle_ddp(im_k)

            k, dense_k, feat_k = self.encoder_k(im_k)  # keys: NxC
            k = nn.functional.normalize(k, dim=1)
            dense_k, feat_k = dense_k.view(n, dim_dense, -1), feat_k.view(n, c, -1)
            dense_k_norm = nn.functional.normalize(dense_k, dim=1)

            # undo shuffle
            k, feat_k, dense_k_norm = self._batch_unshuffle_ddp(
                    k, feat_k, dense_k_norm, idx_unshuffle)

            ## match
            feat_q_norm = nn.functional.normalize(feat_q, dim=1)
            feat_k_norm = nn.functional.normalize(feat_k, dim=1)
            cosine = torch.einsum('nca,ncb->nab', feat_q_norm, feat_k_norm)
            pos_idx = cosine.argmax(dim=-1)
            dense_k_norm = dense_k_norm.gather(2, pos_idx.unsqueeze(1).expand(-1, dim_dense, -1))

        # compute logits
        # Einstein sum is more intuitive
        # positive logits: Nx1
        l_pos = torch.einsum('nc,nc->n', [q, k]).unsqueeze(-1)
        # negative logits: NxK
        l_neg = torch.einsum('nc,ck->nk', [q, self.queue.clone().detach()])

        # logits: Nx(1+K)
        logits = torch.cat([l_pos, l_neg], dim=1)
        # apply temperature
        logits /= self.T
        # labels: positive key indicators
        labels = torch.zeros(logits.shape[0], dtype=torch.long).cuda()

        loss_cls = self.crit_cls(logits, labels)

        ## densecl logits
        d_pos = torch.einsum('ncm,ncm->nm', dense_q, dense_k_norm).unsqueeze(1)
        d_neg = torch.einsum('ncm,ck->nkm', dense_q, self.queue_dense.clone().detach())

        logits_dense = torch.cat([d_pos, d_neg], dim=1)
        logits_dense = logits_dense / self.T
        labels_dense = torch.zeros((n, h*w), dtype=torch.long).cuda()

        loss_dense = self.crit_dense(logits_dense, labels_dense)

        extra = {'qk': [q, k], 'dense_qk': [dense_q, dense_k_norm],
                'logits': logits , 'labels': labels}

        ## regionCL
        if self.cutmixer:
            loss_cutmix = self.cutmixer.forward_mix(
                    self.encoder_q, im_q, q, k, self.queue.detach())
            extra.update({'loss_cutmix': loss_cutmix})

        # dequeue and enqueue
        self._dequeue_and_enqueue(k, dense_k)

        return loss_cls, loss_dense, extra

# utils
@torch.no_grad()
def concat_all_gather(tensor):
    """
    Performs all_gather operation on the provided tensors.
    *** Warning ***: torch.distributed.all_gather has no gradient.
    """
    tensors_gather = [torch.ones_like(tensor)
        for _ in range(torch.distributed.get_world_size())]
    torch.distributed.all_gather(tensors_gather, tensor, async_op=False)

    output = torch.cat(tensors_gather, dim=0)
    return output


import speech_recognition as sr
import webbrowser as wb

r1 = sr.Recognizer()
r2 = sr.Recognizer()
r3 = sr.Recognizer()

with sr.Microphone() as source:
    audio = r3.listen(source)
    r2 = sr.Recognizer()
    url = 'https://www.amazon.in/s?k='
    with sr.Microphone() as source:
        print('which product you want to search?')
        audio = r2.listen(source)
        try:
            get = r2.recognize_google(audio)
            print(get)
            wb.get().open_new(url+get)
        except sr.UnknownValueError as e:
            print('error')
        except sr.RequestError as e:
            print('failed'.format(e))


#Import dependencies
import csv
import os
import sys
import plotly
import plotly.graph_objs as go
import plotly.io as pio

plotly.io.orca.config.executable = '/home/neda-anres/miniconda2/bin/orca'

#Initialization of constants
lgus = ["magsingal", "dinapigue", "masinloc", "nasugbu", "jomalig",
            "quezon", "gasan", "mansalay", "jose-panganiban", "mercedes",
            "claveria", "anini-y", "carles", "toboso", "daanbantayan",
            "bien-unido", "pagsanghan", "arteche", "macarthur",
            "baliguian", "dipolog", "rtlim", "maasim", "socorro",
            "lcli", "lchi", "hcli", "hchi", "universe"]
scenarios = ["bau", "fish-catch", "fisher-revenue", "both"]
criteria = ["baseline", "adoption", "accessibility", "sufficiency",
            "sustainability"]

#Error checking of argument count
if not len(sys.argv) == 4:
    raise ValueError('Usage: python image-builder.py <lgu> <scenario> <criteria>.')

#Assigning of command line arguments
lgu = sys.argv[1]
scenario = sys.argv[2]
criterion = sys.argv[3]
download = sys.argv[4]

#Checking validity of command line arguments
if lgu not in lgus:
    raise ValueError('Chosen LGU not in proper list of LGUs.')
if scenario not in scenarios:
    raise ValueError('Chosen scenario not in proper list of scenarios.')
if criterion not in criteria:
    raise ValueError('Chosen criterion not in proper list of criteria.')

#File input
space = " "
fileDir = os.path.dirname(os.path.realpath('__file__'))
fileName = "rpg_" + criterion + "_results/" + criterion + "-" + lgu + "-" + scenario + ".csv"

#Initialization of list variables
lines = []

pf = []
nf = []
g = []
cw = []
ps = []
lb = []

pf_total = []
nf_total = []
g_total = []
cw_total = []
ps_total = []
lb_total = []

#Parsing the files
#All lines of the CSV file are contained in the list named 'lines'
with open(os.path.join(fileDir, fileName)) as f:
    for row in f:
        lines.append(row.split(","))

#How to process each line
#There are 25 lines, for 25 ticks.
#Tick number 1 starts at line 28 (zero indexing is used)
ticks = 25
for tick in range(0, ticks):
    #Get the line of the CSV
    x = lines[28 + tick]

    #Remove first column
    del x[0]

    #Remove stray new lines and quotation marks
    x = [s.replace('\"', '') for s in x]
    x = [s.replace('\n', '') for s in x]
    x = [float(s) for s in x]

    #Get the total scores for all agents for each of the financing scheme
    for i in range(6, len(x), 42):
        pf.append(x[i+(7*0)])
        nf.append(x[i+(7*1)])
        g.append(x[i+(7*2)])
        cw.append(x[i+(7*3)])
        ps.append(x[i+(7*4)])
        lb.append(x[i+(7*5)])

    #Set up the running totals of the financing schemes for all experiments
    l = {'pf': 0, 'nf': 0, 'g': 0, 'cw': 0, 'ps': 0, 'lb': 0}

    #For all experiments
    for i in range(0, 100):
        #Get the score for each scheme for each experiment
        #Add the score to the running total
        l['pf'] += pf[i]
        l['nf'] += nf[i]
        l['g'] +=  g[i]
        l['cw'] += cw[i]
        l['ps'] += ps[i]
        l['lb'] += lb[i]

    #Compute for the average total score for all experiments, across all persons
    for k, v in l.items():
        l[k] /= 10000

    #Get the average score for all experiments
    pf_total.append(l['pf'])
    nf_total.append(l['nf'])
    g_total.append(l['g'])
    cw_total.append(l['cw'])
    ps_total.append(l['ps'])
    lb_total.append(l['lb'])

    l.clear()
    pf = []
    nf = []
    g = []
    cw = []
    ps = []
    lb = []

#Data for graphs
timestep = range(1, ticks + 1)

#Create traces
trace_pf = go.Scatter(
    x = timestep,
    y = pf_total,
    mode = 'markers',
    name = 'Public Funds'
)

trace_nf = go.Scatter(
    x = timestep,
    y = nf_total,
    mode = 'markers',
    name = 'National Funds'
)

trace_g = go.Scatter(
    x = timestep,
    y = g_total,
    mode = 'markers',
    name = 'Grants'
)

trace_cw = go.Scatter(
    x = timestep,
    y = cw_total,
    mode = 'markers',
    name = 'Credit Windows'
)

trace_ps = go.Scatter(
    x = timestep,
    y = ps_total,
    mode = 'markers',
    name = 'Private Sector'
)

trace_lb = go.Scatter(
    x = timestep,
    y = lb_total,
    mode = 'markers',
    name = 'Local Bonds'
)

label_lgu = {
    "magsingal": "Magsingal, Ilocos Sur",
    "dinapigue": "Dinapigue, Isabela",
    "masinloc": "Masinloc, Zambales",
    "nasugbu": "Nasugbu, Batangas",
    "jomalig": "Jomalig, Quezon Province",
    "quezon": "Quezon, Palawan",
    "gasan": "Gasan, Marinduque",
    "mansalay": "Mansalay, Oriental Mindoro",
    "jose-panganiban": "Jose Panganiban, Camarines Norte",
    "mercedes": "Mercedes, Camarines Norte",
    "claveria": "Claveria, Misamis Oriental",
    "anini-y": "Anini-y, Antique",
    "carles": "Carles, Iloilo",
    "toboso": "Toboso, Negros Occidental",
    "daanbantayan": "Daanbantayan, Cebu",
    "bien-unido": "Bien Unido, Bohol",
    "pagsanghan": "Pagsanghan, Samar",
    "arteche": "Arteche, Eastern Samar",
    "macarthur": "MacArthur, Leyte",
    "baliguian": "Baliguian, Zamboanga del Norte",
    "dipolog": "Dipolog City, Zamboanga del Norte",
    "rtlim": "Roseller T. Lim, Zamboanga Sibugay",
    "maasim": "Maasim, Sarangani",
    "socorro": "Socorro, Surigao del Norte",
    "lcli": "Low Cost Low Impact Municipalities",
    "lchi": "Low Cost High Impact Municipalities",
    "hcli": "High Cost Low Impact Municipalities",
    "hchi": "High Cost High Impact Municipalities",
    "universe": "All"
}

label_scenario = {
    "bau": "a Business as Usual",
    "fish-catch": "a Fish Catch Sustainability",
    "fisher-revenue": "a Fisher Net Revenue Maximized",
    "both": "Both"
}

label_criteria = {
    "baseline": "with Equal Weights",
    "accessibility": "Prioritizing Fund Accessibility",
    "adoption": "Prioritizing Fund Adoption",
    "sustainability": "Prioritizing Fund Sustainability",
    "sufficiency": "Prioritizing Fund Sufficiency"
}

title = 'RPG Simulation Results for '
title = title + label_lgu[lgu] + ' in<br>' + label_scenario[scenario]
title = title + ' Scenario '+ label_criteria[criterion] +' (100 replicates)'

filename = 'rpg_' + criterion + '_graphs/' + lgu + '-' + scenario + '.html'
if not os.path.exists('rpg_' + criterion + '_graphs/images'):
    os.mkdir('rpg_' + criterion + '_graphs/images')
image_name = 'rpg_' + criterion + '_graphs/images/' + lgu + '-' + scenario + '.png'

#Create graph
data = [trace_pf, trace_nf, trace_g, trace_cw, trace_ps, trace_lb]
layout = dict(
    title = title,
    yaxis = dict(title = "Average Score (across replicates)"),
    xaxis = dict(title = "Run Number")
)
fig = go.Figure(data = data, layout = layout)

plotly.offline.plot({
        "data": data,
        "layout": layout
    }, filename=filename, auto_open=False)
pio.write_image(fig, image_name)


import fiona
import shapely.geometry
import numpy as np
from netCDF4 import Dataset
import xarray as xr
from collections import OrderedDict
import rasterio.features
import shapely.geometry as geometry
from affine import Affine

#~ input_name="/work/ALT/swot/swotpub/SWOT_Simulator_data/input_data/garonne_aval/gdem-dem-truth/gdem-dem-garonne_aval-0.nc"
#~ input_name="/work/ALT/swot/swotpub/SWOT_Simulator_data/input_data/camargue/gdem-dem-truth/gdem-dem-camargue-0.nc"
input_name="/work/ALT/swot/swotpub/SWOT_Simulator_data/input_data/sacramento/gdem-dem-truth/gdem-dem-sacramento-1.nc"

#~ output_name="/work/ALT/swot/swotdev/desrochesd/swot-hydrology-toolbox/scripts/tools/camargue.shp"
output_name="/work/ALT/swot/swotdev/desrochesd/swot-hydrology-toolbox/scripts/tools/sacramento.shp"

source_driver = 'ESRI Shapefile'
source_crs = {'init': 'epsg:4326'}
dest_schema = {'properties': OrderedDict([('code', 'int:10'), ('basin_id', 'int:10'), ('name', 'str:254'), ('other_name', 'str:254'), ('reach_lake', 'str:254'), ('ref_height', 'str:254'), ('ref_date', 'int:10'), ('geoid_map', 'str:254'), ('ice_probab', 'str:254'), ('id', 'int:10')]), 'geometry': 'Polygon'}

water_label = 1

ds = xr.open_dataset(input_name)
water_mask = ds.variables['landtype']
latitude = ds.variables['latitude']
longitude = ds.variables['longitude']

water_pixel = np.where(water_mask.values == water_label)
water_pixel_latitude, water_pixel_longitude = latitude.values[water_pixel[0]], longitude.values[water_pixel[1]]

mypoly=[]

dlon = longitude[1]-longitude[0]
dlat = latitude[1]-latitude[0]

tr = Affine(dlon,0,longitude[0]-0.5*dlon,0,dlat,latitude[0]-0.5*dlat)

shapes = rasterio.features.shapes(water_mask.values, transform=tr)
polygons =[geometry.Polygon(shape[0]["coordinates"][0]) for shape in shapes if shape[1] == 1]

with fiona.open(output_name,'w', driver=source_driver, crs=source_crs, schema = dest_schema) as c:

    for p in polygons:
            #~ point = geometry.Point(float(water_pixel_longitude[i]), float(water_pixel_latitude[i]))
            prop = {'code':None, 'basin_id': None, 'name':None,'other_name':None, 'reach_lake':None,'ref_height':None,'ref_date':None,'geoid_map':None,'ice_probab':None,'id':None}
            c.write({'geometry': geometry.mapping(p), 'properties': prop})


import threading

import PyQt5.Qt
from PyQt5 import QtWidgets
from PyQt5 import Qt
from PyQt5.QtCore import Qt
from PyQt5.QtCore import QMimeData
from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsScene
from PyQt5.QtGui import QDrag, QPalette, QColor, QMouseEvent, QPaintEvent
import sys

import pyedid.helpers
import pyedid
from pyedid import Registry
import randr

class DisplayWindow(QMainWindow):
    DisplayWidgets = []
    def __init__(self):
        super(DisplayWindow, self).__init__()
        self.setGeometry(200, 200, 300, 300)
        self.setWindowTitle("Display")
        self.setAcceptDrops(True)
        global MainWindow
        MainWindow = self
        self.edidRegistry = Registry.from_csv('edid.csv')
        self.InitUi()

    def InitUi(self):

        for display in randr.connected_screens():
            if display.is_enabled():
                dv = self.AddScreenWidget(display)
                self.DisplayWidgets.append(dv)
        self.selectedDisplayName = QtWidgets.QLabel(self)
        self.selectedDisplayName.move(100, 150)
        self.selectedDisplayName.setText("Display")
        self.selectedresolutionSelector = QtWidgets.QComboBox(self)
        self.selectedresolutionSelector.move(100, 170)
        self.primaryDisplayCheckbox = QtWidgets.QCheckBox("Primary Display",self)
        self.primaryDisplayCheckbox.move(100, 190)
        self.primaryDisplayCheckbox.updateGeometry()

        #self.scene = QGraphicsScene(self)
        #prox = self.scene.addWidget(self.DisplayWidgets[0])
        #prox.setRotation(45)

        #label = QtWidgets.QLabel()
        #label.setText("ASDASD")
#
        #graphicsview = QtWidgets.QGraphicsView()
        #scene = QtWidgets.QGraphicsScene(graphicsview)
        #graphicsview.setScene(scene)
#
        #proxy = QtWidgets.QGraphicsProxyWidget()
        #proxy.setWidget(label)
        #proxy.setTransformOriginPoint(proxy.boundingRect().center())
        #scene.addItem(proxy)
        #proxy.setRotation(45)

        self.SelectScreen(self.DisplayWidgets[0])
        self.applyButton = QtWidgets.QPushButton(self)
        self.applyButton.setText("Apply")
        self.applyButton.move(0, 250)

    def AddScreenWidget(self, screen):
        v = DisplayWidget()
        v.setParent(self)
        v.SetScreen(screen)
        return v
    def SelectScreen(self, displayWidget):

        self.selectedDisplayName.setText(displayWidget.randr_screen.name)
        self.selectedDisplayName.updateGeometry()
        self.selectedresolutionSelector.clear()
        for asd in displayWidget.randr_screen.supported_modes:
            if(asd):
                self.selectedresolutionSelector.addItem(str(asd.resolution()))

    def dragEnterEvent(self, event):
        event.accept()
        #print("I AM DRAGGED")

    def dropEvent(self, event):
        event.accept()
        #print("I AM DROP")
        for widget in self.DisplayWidgets:
            if(widget.isDragged):
                widget.move(event.pos()-widget.initialPos)
                widget.isDragged = False

    def dragMoveEvent(self, event):
        event.accept()
        for widget in self.DisplayWidgets:
            if(widget.isDragged):
                widget.move(event.pos()-widget.initialPos)

class DisplayWidget(QtWidgets.QWidget):
    def __init__(self):
        super(DisplayWidget, self).__init__()
        self.setGeometry(0, 0, 100, 100)
        self.manufacturerLabel = QtWidgets.QLabel(self)
        self.manufacturerLabel.setText("manufacturer")

        self.nameLabel = QtWidgets.QLabel(self)
        self.nameLabel.setText("model")
        self.nameLabel.move(0, 15)

        self.connectorLabel = QtWidgets.QLabel(self)
        self.connectorLabel.setText("connector")
        self.connectorLabel.move(0, 15)

        self.pal = QPalette()
        self.pal.setColor(QPalette.Background, QColor(200, 200, 200))
        self.setAutoFillBackground(True)
        self.setPalette(self.pal)
        self.setAcceptDrops(True)
        self.show()
        self.isDragged=False

    def SetScreen(self, screen):

        # print(screen.rotation)
        #rint(screen.current)
        self.mode = screen.get_current_mode()
        self.screen = screen
        print(screen)
        print(self.mode)

        #set colorspace
        #xrandr --output HDMI-1 --set "Colorspace" "DCI-P3_RGB_D65"
        #get rot
        #xrandr --query --verbose | grep "LVDS-1"
        #self.SetRotation(45)

        print(screen.get_rotation())
        if(screen.rotation == 1 or screen.rotation == 3):
            self.setGeometry(self.screen.px/15, self.screen.py/15, self.mode.width/15, self.mode.height/15)
        else:
            self.setGeometry(self.screen.px/15, self.screen.py/15, self.mode.height / 15, self.mode.width / 15)

        #self.setGeometry(self.screen.px/15, self.screen.py/15)
        #normal = 1
        #right = 4
        #inverted = 3
        #left = 2
        self.edid = pyedid.parse_edid(randr.get_edid_for_output(self.screen.name), MainWindow.edidRegistry)

        #print(self.edid)

        self.manufacturerLabel.setText(self.edid.manufacturer.split(" ")[0]+" "+self.edid.name)
        self.nameLabel.setText("")
        self.connectorLabel.setText("Display Port")
        self.nameLabel.adjustSize()
        self.manufacturerLabel.adjustSize()
        self.connectorLabel.adjustSize()

    def mousePressEvent(self, event: QMouseEvent):
        MainWindow.SelectScreen(self)

    def mouseMoveEvent(self, event:QMouseEvent):
        if(event.buttons() == Qt.LeftButton):
            #print(333)
            self.initialPos = event.pos()
            self.isDragged = True
            mimeData = QMimeData()
            drag = QDrag(self)
            drag.setMimeData(mimeData)
            dropAction = drag.exec_(Qt.MoveAction)

def main():
    app = QApplication(sys.argv)
    win = DisplayWindow()
    win.show()
    sys.exit(app.exec_())
main()


from django.shortcuts import render
from django.views.generic import View
from .models import *
from .utils import get_bankcard_transactions
from .forms import *

class ControlPanel(View):
    def get(self, request):
        context = {
            'title': 'Control Panel',
            'bankcards': BankCard.objects.all()
        }
        return render(request, 'base/panel.html', context=context)

class CardTransactionsView(View):
    def get(self, request, card_id):
        n = Transaction.objects.all().count()
        data = get_bankcard_transactions(BankCard.objects.get(id=card_id).account, n)
        print(data)
        context = {
            'card_id': card_id,
            'data': data,
            'title': 'Bank Card Transactions',
        }
        return render(request, 'base/card_transactions.html', context=context)

class InfoListView(View):

    def get(self, request):
        users = User.objects.all()
        accounts = Account.objects.all()
        bankcards = BankCard.objects.all()
        transactions = Transaction.objects.all()
        inner_transactions = InnerTransaction.objects.all()
        to_outer_transactions = ToOuterTransaction.objects.all()
        from_outer_transactions = FromOuterTransaction.objects.all()

        context = {
            'users': users,
            'accounts': accounts,
            'bankcards': bankcards,
            'transactions': transactions,
            'inner_transactions': inner_transactions,
            'to_outer_transactions': to_outer_transactions,
            'from_outer_transactions': from_outer_transactions,
            'title': 'System Info',
        }
        return render(request, 'base/info.html', context=context)

class MasterAccountRefill(View):

    def get(self, request):
        context = {
            'form': MasterAccountRefillForm,
            'balance': Account.objects.get(id=2).balance,
            'title': 'Master Account Refill',
        }
        return render(request, 'base/master_account_refill.html', context=context)

    def post(self, request):
        refill = request.POST.get("refill")
        master_account = Account.objects.get(id=2)
        master_account.balance += int(refill)
        master_account.save()
        context = {
            'form': MasterAccountRefillForm,
            'message': f'Master Account Delta = {refill} RUB',
            'balance': Account.objects.get(id=2).balance,
            'title': 'Master Account Refill',
        }
        return render(request, 'base/master_account_refill.html', context=context)

class NewOrgAccount(View):

    def get(self, request):
        context = {
            'form': NewOrgForm,
            'title': 'New Organization Account',
            'message': 'Only RUB account creation available',
        }
        return render(request, 'base/new_org_account.html', context=context)

    def post(self, request):
        owner_info = request.POST.get("INN")
        balance = request.POST.get("balance")
        currency = "RUB"
        account = Account.objects.create(owner_info=owner_info, balance=balance, currency=currency)
        context = {
            'message': 'New Organization Account created:',
            'account': account,
            'title': 'New Organization Account',
        }
        return render(request, 'base/new_org_account.html', context=context)


'''
Created on Dec 4, 2019

@author: Matthew Cook
'''

import sys
from socket import socket
from uuid import getnode
from ast import literal_eval
import traceback
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMessageBox

########################### PyQt5 UI ###############################################################

# -*- coding: utf-8 -*-
#
# Created by: PyQt5 UI code generator 5.13.0

##################### MAIN WINDOW ############################
class Ui_LocalProxyNetwork(object):
    def setupUi(self, LocalProxyNetwork):
        LocalProxyNetwork.setObjectName("LocalProxyNetwork")
        LocalProxyNetwork.resize(837, 659)
        self.centralwidget = QtWidgets.QWidget(LocalProxyNetwork)
        self.centralwidget.setObjectName("centralwidget")

        self.btnRegister = QtWidgets.QPushButton(self.centralwidget)
        self.btnRegister.setGeometry(QtCore.QRect(120, 60, 161, 71))
        self.btnRegister.setObjectName("btnRegister")
        self.btnRegister.clicked.connect(self.registerWindow)

        self.btnDeregister = QtWidgets.QPushButton(self.centralwidget)
        self.btnDeregister.setGeometry(QtCore.QRect(120, 280, 161, 71))
        self.btnDeregister.setObjectName("btnDeregister")
        self.btnDeregister.clicked.connect(self.deregisterWindow)

        self.btnQuery = QtWidgets.QPushButton(self.centralwidget)
        self.btnQuery.setGeometry(QtCore.QRect(560, 280, 161, 71))
        self.btnQuery.setObjectName("btnQuery")
        self.btnQuery.clicked.connect(self.queryWindow)

        self.btnMessage = QtWidgets.QPushButton(self.centralwidget)
        self.btnMessage.setGeometry(QtCore.QRect(560, 60, 161, 71))
        self.btnMessage.setObjectName("btnMessage")
        self.btnMessage.clicked.connect(self.messageWindow)

        self.btnQuit = QtWidgets.QPushButton(self.centralwidget)
        self.btnQuit.setGeometry(QtCore.QRect(330, 180, 161, 71))
        self.btnQuit.setObjectName("btnQuit")
        self.btnQuit.clicked.connect(QUIT)

        self.lbCommand = QtWidgets.QLabel(self.centralwidget)
        self.lbCommand.setGeometry(QtCore.QRect(350, 0, 160, 61))
        font = QtGui.QFont()
        font.setPointSize(18)
        font.setBold(True)
        self.lbCommand.setFont(font)
        self.lbCommand.setObjectName("lbCommand")

        self.scrollArea = QtWidgets.QScrollArea(self.centralwidget)
        self.scrollArea.setGeometry(QtCore.QRect(50, 380, 741, 251))
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setObjectName("scrollArea")
        self.scrollAreaWidgetContents = QtWidgets.QWidget()
        self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 739, 249))
        self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
        self.lbOutput = QtWidgets.QLabel(self.scrollAreaWidgetContents)
        self.lbOutput.setGeometry(QtCore.QRect(300, 0, 111, 31))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.lbOutput.setFont(font)
        self.lbOutput.setObjectName("lbOutput")
        self.textBrowser = QtWidgets.QTextBrowser(self.scrollAreaWidgetContents)
        self.textBrowser.setGeometry(QtCore.QRect(10, 30, 721, 211))
        self.textBrowser.setObjectName("textBrowser")
        self.textBrowser.setFont(font)
        self.scrollArea.setWidget(self.scrollAreaWidgetContents)
        LocalProxyNetwork.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(LocalProxyNetwork)
        self.statusbar.setObjectName("statusbar")
        LocalProxyNetwork.setStatusBar(self.statusbar)

        self.retranslateUi(LocalProxyNetwork)
        QtCore.QMetaObject.connectSlotsByName(LocalProxyNetwork)

    def retranslateUi(self, LocalProxyNetwork):
        _translate = QtCore.QCoreApplication.translate
        LocalProxyNetwork.setWindowTitle(_translate("LocalProxyNetwork", "MainWindow"))
        self.btnRegister.setText(_translate("LocalProxyNetwork", "REGISTER"))
        self.btnDeregister.setText(_translate("LocalProxyNetwork", "DEREGISTER"))
        self.btnQuery.setText(_translate("LocalProxyNetwork", "QUERY"))
        self.btnMessage.setText(_translate("LocalProxyNetwork", "MESSAGE"))
        self.btnQuit.setText(_translate("LocalProxyNetwork", "QUIT"))
        self.lbCommand.setText(_translate("LocalProxyNetwork", "Commands:"))
        self.lbOutput.setText(_translate("LocalProxyNetwork", "Console Output"))
        self.textBrowser.setHtml(_translate("LocalProxyNetwork", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<meta name=\"qrichtext\" content=\"1\" />\n"
"
Welcome to Local Proxy Network UI version 1.0
\n"
"
Output is below:
"))

    def showPop(self):
        msg = QMessageBox()
        msg.setWindowTitle("Register Stuff")
        msg.setText("Do some register stuff")
        ex = msg.exec_()

    def registerWindow(self):
        self.registerWindow = QtWidgets.QMainWindow()
        ui = Ui_RegisterWindow()
        ui.setupUi(self.registerWindow)
        self.registerWindow.show()

    def deregisterWindow(self):
        self.deregisterWindow = QtWidgets.QMainWindow()
        ui = Ui_DeregisterWindow()
        ui.setupUi(self.deregisterWindow)
        self.deregisterWindow.show()

    def messageWindow(self):
        self.messageWindow = QtWidgets.QMainWindow()
        ui = Ui_MessageWindow()
        ui.setupUi(self.messageWindow)
        self.messageWindow.show()

    def queryWindow(self):
        self.queryWindow = QtWidgets.QMainWindow()
        ui = Ui_QueryWindow()
        ui.setupUi(self.queryWindow)
        self.queryWindow.show()

##################### REGISTER WINDOW ############################

class Ui_RegisterWindow(object):
    def setupUi(self, RegisterWindow):
        RegisterWindow.setObjectName("RegisterWindow")
        RegisterWindow.resize(491, 262)
        self.centralwidget = QtWidgets.QWidget(RegisterWindow)
        self.centralwidget.setObjectName("centralwidget")

        self.lblRegister = QtWidgets.QLabel(self.centralwidget)
        self.lblRegister.setGeometry(QtCore.QRect(10, 50, 291, 61))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.lblRegister.setFont(font)
        self.lblRegister.setObjectName("lblRegister")

        self.editRegister = QtWidgets.QTextEdit(self.centralwidget)
        self.editRegister.setGeometry(QtCore.QRect(310, 40, 151, 71))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.editRegister.setFont(font)
        self.editRegister.setObjectName("editRegister")

        self.btnRegister = QtWidgets.QPushButton(self.centralwidget)
        self.btnRegister.setGeometry(QtCore.QRect(170, 140, 111, 61))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.btnRegister.setFont(font)
        self.btnRegister.setObjectName("btnRegister")
        self.btnRegister.clicked.connect(lambda: self.registerDevice())

        RegisterWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(RegisterWindow)
        self.statusbar.setObjectName("statusbar")
        RegisterWindow.setStatusBar(self.statusbar)

        self.retranslateUi(RegisterWindow)
        QtCore.QMetaObject.connectSlotsByName(RegisterWindow)

    def retranslateUi(self, RegisterWindow):
        _translate = QtCore.QCoreApplication.translate
        RegisterWindow.setWindowTitle(_translate("RegisterWindow", "MainWindow"))
        self.lblRegister.setText(_translate("RegisterWindow", "Please Enter a nickname for your device:"))
        self.btnRegister.setText(_translate("RegisterWindow", "Register"))

    def registerDevice(self):
        deviceId = self.editRegister.toPlainText()
        REGISTER(deviceId)

##################### DEREGISTER WINDOW ############################

class Ui_DeregisterWindow(object):
    def setupUi(self, DeregisterWindow):
        DeregisterWindow.setObjectName("DeregisterWindow")
        DeregisterWindow.resize(491, 261)
        self.centralwidget = QtWidgets.QWidget(DeregisterWindow)
        self.centralwidget.setObjectName("centralwidget")

        self.lblDeregister = QtWidgets.QLabel(self.centralwidget)
        self.lblDeregister.setGeometry(QtCore.QRect(10, 50, 301, 41))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.lblDeregister.setFont(font)
        self.lblDeregister.setObjectName("lblDeregister")

        self.editDeregister = QtWidgets.QTextEdit(self.centralwidget)
        self.editDeregister.setGeometry(QtCore.QRect(320, 40, 151, 71))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.editDeregister.setFont(font)
        self.editDeregister.setObjectName("editDeregister")

        self.btnDeregister = QtWidgets.QPushButton(self.centralwidget)
        self.btnDeregister.setGeometry(QtCore.QRect(170, 140, 111, 61))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.btnDeregister.setFont(font)
        self.btnDeregister.setObjectName("btnDeregister")
        self.btnDeregister.clicked.connect(lambda: self.deregisterDevice())

        DeregisterWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(DeregisterWindow)
        self.statusbar.setObjectName("statusbar")
        DeregisterWindow.setStatusBar(self.statusbar)

        self.retranslateUi(DeregisterWindow)
        QtCore.QMetaObject.connectSlotsByName(DeregisterWindow)

    def retranslateUi(self, DeregisterWindow):
        _translate = QtCore.QCoreApplication.translate
        DeregisterWindow.setWindowTitle(_translate("DeregisterWindow", "MainWindow"))
        self.lblDeregister.setText(_translate("DeregisterWindow", "Please enter the nickname for your device:"))
        self.btnDeregister.setText(_translate("DeregisterWindow", "Deregister"))

    def deregisterDevice(self):
        deviceId = self.editDeregister.toPlainText()
        DEREGISTER(deviceId)

##################### MESSAGE WINDOW ############################

class Ui_MessageWindow(object):
    def setupUi(self, MessageWindow):
        MessageWindow.setObjectName("MessageWindow")
        MessageWindow.resize(487, 380)
        self.centralwidget = QtWidgets.QWidget(MessageWindow)
        self.centralwidget.setObjectName("centralwidget")

        self.lblTitle = QtWidgets.QLabel(self.centralwidget)
        self.lblTitle.setGeometry(QtCore.QRect(170, 10, 131, 31))
        font = QtGui.QFont()
        font.setPointSize(14)
        font.setBold(True)
        font.setWeight(75)
        self.lblTitle.setFont(font)
        self.lblTitle.setObjectName("lblTitle")

        self.lblTo = QtWidgets.QLabel(self.centralwidget)
        self.lblTo.setGeometry(QtCore.QRect(110, 50, 31, 16))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.lblTo.setFont(font)
        self.lblTo.setObjectName("lblTo")

        self.lblFrom = QtWidgets.QLabel(self.centralwidget)
        self.lblFrom.setGeometry(QtCore.QRect(90, 110, 47, 14))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.lblFrom.setFont(font)
        self.lblFrom.setObjectName("lblFrom")

        self.editTo = QtWidgets.QTextEdit(self.centralwidget)
        self.editTo.setGeometry(QtCore.QRect(140, 50, 161, 31))
        self.editTo.setObjectName("editTo")
        self.editTo.setFont(font)

        self.editMessage = QtWidgets.QTextEdit(self.centralwidget)
        self.editMessage.setGeometry(QtCore.QRect(140, 150, 251, 111))
        self.editMessage.setObjectName("editMessage")
        self.editMessage.setFont(font)

        self.editFrom = QtWidgets.QTextEdit(self.centralwidget)
        self.editFrom.setGeometry(QtCore.QRect(140, 100, 161, 31))
        self.editFrom.setObjectName("editFrom")
        self.editFrom.setFont(font)

        self.lblMessage = QtWidgets.QLabel(self.centralwidget)
        self.lblMessage.setGeometry(QtCore.QRect(70, 150, 71, 21))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.lblMessage.setFont(font)
        self.lblMessage.setObjectName("lblMessage")

        self.btnDeregister = QtWidgets.QPushButton(self.centralwidget)
        self.btnDeregister.setGeometry(QtCore.QRect(200, 280, 111, 61))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.btnDeregister.setFont(font)
        self.btnDeregister.setObjectName("btnDeregister")
        self.btnDeregister.clicked.connect(lambda: self.messageDevice())

        MessageWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(MessageWindow)
        self.statusbar.setObjectName("statusbar")
        MessageWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MessageWindow)
        QtCore.QMetaObject.connectSlotsByName(MessageWindow)

    def retranslateUi(self, MessageWindow):
        _translate = QtCore.QCoreApplication.translate
        MessageWindow.setWindowTitle(_translate("MessageWindow", "MainWindow"))
        self.lblTitle.setText(_translate("MessageWindow", "New Message"))
        self.lblTo.setText(_translate("MessageWindow", "To:"))
        self.lblFrom.setText(_translate("MessageWindow", "From:"))
        self.editMessage.setHtml(_translate("MessageWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<meta name=\"qrichtext\" content=\"1\" />\n"
"

"))
        self.lblMessage.setText(_translate("MessageWindow", "Message:"))
        self.btnDeregister.setText(_translate("MessageWindow", "Send"))

    def messageDevice(self):
        toId = self.editTo.toPlainText()
        fromId = self.editFrom.toPlainText()
        message = self.editMessage.toPlainText()
        MSG(fromId, toId, message)

##################### QUERY WINDOW ############################

class Ui_QueryWindow(object):
    def setupUi(self, QueryWindow):
        QueryWindow.setObjectName("QueryWindow")
        QueryWindow.resize(474, 332)
        self.centralwidget = QtWidgets.QWidget(QueryWindow)
        self.centralwidget.setObjectName("centralwidget")

        self.lblTitle = QtWidgets.QLabel(self.centralwidget)
        self.lblTitle.setGeometry(QtCore.QRect(190, 10, 111, 31))
        font = QtGui.QFont()
        font.setPointSize(14)
        font.setBold(True)
        font.setWeight(75)
        self.lblTitle.setFont(font)
        self.lblTitle.setObjectName("lblTitle")

        self.lblDeviceId = QtWidgets.QLabel(self.centralwidget)
        self.lblDeviceId.setGeometry(QtCore.QRect(70, 50, 111, 21))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.lblDeviceId.setFont(font)
        self.lblDeviceId.setObjectName("lblDeviceId")

        self.lblMessage = QtWidgets.QLabel(self.centralwidget)
        self.lblMessage.setGeometry(QtCore.QRect(130, 100, 51, 21))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.lblMessage.setFont(font)
        self.lblMessage.setObjectName("lblMessage")

        self.btnQuery = QtWidgets.QPushButton(self.centralwidget)
        self.btnQuery.setGeometry(QtCore.QRect(180, 230, 111, 61))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.btnQuery.setFont(font)
        self.btnQuery.setObjectName("btnQuery")
        self.btnQuery.clicked.connect(lambda: self.queryDevice())

        self.editDeviceId = QtWidgets.QTextEdit(self.centralwidget)
        self.editDeviceId.setGeometry(QtCore.QRect(180, 50, 161, 31))
        self.editDeviceId.setObjectName("editDeviceId")
        self.editDeviceId.setFont(font)

        self.lblType = QtWidgets.QLabel(self.centralwidget)
        self.lblType.setGeometry(QtCore.QRect(190, 100, 201, 21))
        font = QtGui.QFont()
        font.setPointSize(12)
        font.setBold(True)
        font.setWeight(75)
        self.lblType.setFont(font)
        self.lblType.setObjectName("lblType")

        self.sliderType = QtWidgets.QSlider(self.centralwidget)
        self.sliderType.setGeometry(QtCore.QRect(120, 160, 241, 21))
        self.sliderType.setMaximum(1)
        self.sliderType.setOrientation(QtCore.Qt.Horizontal)
        self.sliderType.setObjectName("sliderType")
        self.sliderType.valueChanged.connect(lambda: self.adjustTypeLabel())

        QueryWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(QueryWindow)
        self.statusbar.setObjectName("statusbar")
        QueryWindow.setStatusBar(self.statusbar)

        self.retranslateUi(QueryWindow)
        QtCore.QMetaObject.connectSlotsByName(QueryWindow)

    def retranslateUi(self, QueryWindow):
        _translate = QtCore.QCoreApplication.translate
        QueryWindow.setWindowTitle(_translate("QueryWindow", "MainWindow"))
        self.lblTitle.setText(_translate("QueryWindow", "Data Query"))
        self.lblDeviceId.setText(_translate("QueryWindow", "Target Device:"))
        self.lblMessage.setText(_translate("QueryWindow", "Type:"))
        self.btnQuery.setText(_translate("QueryWindow", "Send"))
        self.lblType.setText(_translate("QueryWindow", "Mailbox"))

    def adjustTypeLabel(self):
        numType = self.sliderType.value()
        if(numType is 0):
            self.lblType.setText("Mailbox")
        elif(numType is 1):
            self.lblType.setText("Device Information")

    def queryDevice(self):
        numType = self.sliderType.value()
        targetDevice = self.editDeviceId.toPlainText()
        QUERY(numType, targetDevice)

########################### CLIENT FUNCTIONS ###############################################################

#Name: REGISTER
#
#Purpose: Sends request to be registered
#
#Param: in: string representing device's Id (deviceId)
#
#Brief: Sends message to the server to request that device
#       be registered in proxy network
#
#ErrorsHandled: N/A
#
def REGISTER(deviceId):
    myMAC = getnode() #gets the MAC address of device
    myMAC = hex(myMAC)
    myMAC = myMAC[2:] # removes '0x' from the hexadecimal number
    myMAC = ':'.join(format(s, '02x') for s in bytes.fromhex(myMAC)) #seperates hexadecimal into MAC address form
    myMessage = (1, deviceId, myMAC)                                 #(borrowed from stackoverflow)
    myMessage = str.encode(str(myMessage))
    socketTCP.send(myMessage)
    recvData = socketTCP.recv(1024)
    recvData = bytes.decode(recvData)
    recvData = literal_eval(recvData)
    if recvData[0] is 0: #Received message is a ACK
        print("ACK: " + recvData[1] + '\n')
        ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "ACK: " + recvData[1] + "\n" + '\n')
    elif recvData[0] is 1: #Received message is a NACK
        print("NACK: " + recvData[1] + '\n')
        ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "NACK: " + recvData[1] + "\n" + '\n')

#Name: DEREGISTER
#
#Purpose: Sends request to be deregistered
#
#Param: in: string representing device's Id (deviceId)
#
#Brief: Sends message to server so that it can handle removing
#       device register from proxy network
#
#ErrorsHandled: N/A
#
def DEREGISTER(deviceId):
    myMAC = getnode() #gets the MAC address of device
    myMAC = hex(myMAC)
    myMAC = myMAC[2:] # removes '0x' from the hexadecimal number
    myMAC = ':'.join(format(s, '02x') for s in bytes.fromhex(myMAC)) #seperates hexadecimal into MAC address form
    myMessage = (2, deviceId, myMAC)
    myMessage = str.encode(str(myMessage))
    socketTCP.send(myMessage)
    recvData = socketTCP.recv(1024)
    recvData = bytes.decode(recvData)
    recvData = literal_eval(recvData)
    if recvData[0] is 0: #Received message is a ACK
        print("ACK: " + recvData[1] + '\n')
        ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "ACK: " + recvData[1] + "\n" + '\n')
    elif recvData[0] is 1: #Received message is a NACK
        print("NACK: " + recvData[1] + '\n')
        ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "NACK: " + recvData[1] + "\n" + '\n')

#Name: MSG
#
#Purpose: Sends message to another device in the network
#
#Param: in: string representing device's Id (deviceId)
#       in: string representing receiver's Id (receiverId)
#       in: string message being sent (message)
#
#Brief: Sends message to the server so that it can be stored in
#       the mailbox table of database
#
#ErrorsHandled: N/A/
#
def MSG(fromId, toID, message):
    myMessage = (3, fromId, toID, message)
    myMessage = str.encode(str(myMessage))
    socketTCP.send(myMessage)
    recvData = socketTCP.recv(1024)
    recvData = bytes.decode(recvData)
    recvData = literal_eval(recvData)
    if recvData[0] is 0: #Received message is a ACK
        print("ACK: " + recvData[1] + '\n')
        ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "ACK: " + recvData[1] + "\n" + '\n')
    elif recvData[0] is 1:#Received message is a NACK
        print("NACK: " + recvData[1] + '\n')
        ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "NACK: " + recvData[1] + "\n" + '\n')

#Name: QUERY
#
#Purpose: Query database for desired information
#
#Param: in: integer representing type of query (qType)
#       in: string representing device's Id (deviceId)
#
#Brief: Query database for desired information requested by
#       the client
#
#ErrorsHandled:
#
def QUERY(qType, deviceId):
    try:
        if qType is 0:
            print("Mailbox Query")
            ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "Mailbox Query" + "\n")
            myMessage = (4, 0, deviceId)
            myMessage = str.encode(str(myMessage))
            socketTCP.send(myMessage)
            recvData = socketTCP.recv(2048)
            recvData = bytes.decode(recvData)
            recvData = literal_eval(recvData)
            if recvData[0] is 0: #Received message is a ACK
                print("ACK: " + recvData[1])
                ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "ACK: " + recvData[1] + "\n")
                printMailQuery(recvData[2])

            elif recvData[0] is 1:#Received message is a NACK
                print("NACK: " + recvData[1])
                ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "NACK: " + recvData[1] + "\n")
        elif qType is 1:
            print("Device Info Query")
            ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "Device Info Query" + "\n")
            myMessage = (4, 1, deviceId)
            myMessage = str.encode(str(myMessage))
            socketTCP.send(myMessage)
            recvData = socketTCP.recv(1024)
            recvData = bytes.decode(recvData)
            recvData = literal_eval(recvData)

            if recvData[0] is 0: #Received message is a ACK
                print("ACK: " + recvData[1])
                ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "ACK: " + recvData[1] + "\n")
                printDevicesQuery(recvData[2])

            elif recvData[0] is 1:#Received message is a NACK
                print("NACK: " + recvData[1])
                ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "ACK: " + recvData[1] + "\n")
        else:
            print("Inputted type is not a valid query type")
            ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "Inputted type is not a valid query type" + "\n")
    except Exception as e:
            print(traceback.format_exc())

#Name: Quit Application
#
#Purpose: Closes socket connection with network
#
#Param: N/A
#
#Brief: N/A
#
#ErrorsHandled: N/A
#
def QUIT():
    print("Quitting proxy network and closing socket connection")
    ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "Quitting proxy network and closing socket connection" + "\n")
    socketTCP.close()
    sys.exit(app.exec_())

#Name: Print Mail Query
#
#Purpose: Prints the result of a database query for mail
#
#Param: in: 2-D Array representing database query (result)
#
#Brief: N/A
#
#ErrorsHandled: N/A
#
def printMailQuery(result):
    if result is not None:
        for row in result:
            myRow = "To: " + row[1] + "   " + "From: " + row[2] + "    Message: " + row[3] + "   " + "Date: " + row[4]
            print(myRow)
            ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + myRow + "\n")
    print("\n")
    ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "\n")

#Name: Print Devices Query
#
#Purpose: Prints the result of a database query for devices
#
#Param: in: 2-D Array representing database query (result)
#
#Brief: N/A
#
#ErrorsHandled: N/A
#
def printDevicesQuery(result):
    if result is not None:
        for row in result:
            myRow = "DeviceId: " + row[1] + "   " + "MAC: " + row[2] + "   IP: " + row[3] + "   " + "Port: " + row[4]
            print(myRow)
            ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + myRow + "\n")
    print("\n")
    ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "\n")

####################### MAIN PROGRAM #######################################################################

#Code for client to connect to server
socketTCP = socket()
myIP = "192.168.5.5"
myPortNumber = 9999
socketTCP.connect((myIP, myPortNumber))

#Create PyQt5 Window
app = QtWidgets.QApplication(sys.argv)
LocalProxyNetwork = QtWidgets.QMainWindow()
ui = Ui_LocalProxyNetwork()
ui.setupUi(LocalProxyNetwork)
LocalProxyNetwork.show()
ui.textBrowser.setPlainText(ui.textBrowser.toPlainText() + "\n\n")
sys.exit(app.exec_())

#Client Information
#userId = "Kamran's Tower"
#userId2 = "Nope"

#Client Functions Testing
#REGISTER(userId)
#REGISTER(userId2)
#MSG(userId, userId2, "Testing test")
#QUERY(0, userId2)
#QUERY(1, userId2)


"""
grape1.py

Code for analyzing and visualizing HamSCI Grape1 Personal Space Weather Station
Observations.

Contributions by:
    Nathaniel Frissell W2NAF
    Kristina Collins KD8OXT
    Aidan Montare KB3UMD
    David Kazdan AD8Y
    John Gibbons N8OBJ
    Bob Benedict KD8CGH

    July 2022
"""

import os
import sys
import glob
import string
letters = string.ascii_lowercase

import datetime
import pytz

import numpy as np
import pandas as pd
import xarray as xr

import plotly.express as px
import plotly.graph_objects as go

import matplotlib as mpl
import matplotlib.pyplot as plt

from tqdm.auto import tqdm
tqdm.pandas(dynamic_ncols=True)

from scipy.interpolate import interp1d
from scipy import signal

from . import locator
from . import solar
from . import goes
from . import gen_lib as gl

prm_dict = {}

pkey = 'Freq'
prm_dict[pkey] = {}
prm_dict[pkey]['label'] = 'Doppler Shift [Hz]'
prm_dict[pkey]['vmin']  = -0.5
prm_dict[pkey]['vmax']  =  0.5
prm_dict[pkey]['cmap']  = 'bwr_r'
prm_dict[pkey]['title'] = 'Grape Doppler Shift'

pkey = 'Vpk'
prm_dict[pkey] = {}
prm_dict[pkey]['label'] = 'Peak Voltage [V]'

pkey = 'Power_dB'
prm_dict[pkey] = {}
prm_dict[pkey]['label'] = 'Received Power [dB]'
prm_dict[pkey]['title'] = 'Grape Received Power'

pkey = 'LMT'
prm_dict[pkey] = {}
prm_dict[pkey]['label'] = 'Mean Solar Time'

pkey = 'LMT_Hour'
prm_dict[pkey] = {}
prm_dict[pkey]['label'] = 'Solar Mean Time [Hours]'

pkey = 'LMT_Date'
prm_dict[pkey] = {}
prm_dict[pkey]['label'] = 'Date'

pkey = 'UTC_Hour'
prm_dict[pkey] = {}
prm_dict[pkey]['label'] = 'UTC [Hours]'

pkey = 'UTC_Date'
prm_dict[pkey] = {}
prm_dict[pkey]['label'] = 'Date'

pkey = 'lon'
prm_dict[pkey] = {}
prm_dict[pkey]['label'] = 'Longitude'

pkey = 'lat'
prm_dict[pkey] = {}
prm_dict[pkey]['label'] = 'Latitude'

class DataInventory(object):
    def __init__(self,nodes=None,G=None,freq=None,sTime=None,eTime=None,
                    data_path='data',suffix='.csv'):
        """
        Create an inventory of availble grape1 data in the data_path.
        Inventory will be dataframe df attached to DataInventory object.

        data_path: location of grape1 data
        suffix:    suffix of data files. Defaults to '.csv'
	"""
        # Load filenames and create a dataframe.
        fpaths = glob.glob(os.path.join(data_path,'*'+suffix))
        bnames = [os.path.basename(fpath) for fpath in fpaths]
        df = pd.DataFrame({'Filename':bnames})

        # Parse filenames into useful information and place into dataframe.
        df2              = df['Filename'].str.split('_', expand=True)
        df2.columns      =['Datetime', 'Node', 'G', 'Grid Square', 'FRQ', 'Frequency']
        df2              = df2.drop(columns=['FRQ']) # no information in this columnn
        df2["Frequency"] = df2["Frequency"].str.strip(suffix)

        # # Parse Grid Squares
        # lat,lon = locator.gridsquare2latlon(df2['Grid Square'])
        # df2['Latitude']  = lat
        # df2['Longitude'] = lon

        df               = pd.concat([df2, df], axis = 1) # concatenate dataframes horizontally
        df['Datetime']   = pd.to_datetime(df['Datetime']) # cast to datetime
        df['Node']       = df['Node'].str.strip("N")                   # Ditch the leading N on the node numbers
        df['Node']       = df['Node'].astype(str).astype(int)          # Cast node number to int
        df               = df[~df['Frequency'].str.contains('G1')]     # discarding files with naming errors

        # Convert frequency abbreviations to numbers:
        df.loc[df['Frequency'] == 'WWV5',    'Frequency'] = 5e6
        df.loc[df['Frequency'] == 'WWV10',   'Frequency'] = 10e6
        df.loc[df['Frequency'] == 'WWV2p5',  'Frequency'] = 2.5e6
        df.loc[df['Frequency'] == 'WWV15',   'Frequency'] = 15e6
        df.loc[df['Frequency'] == 'WWV20',   'Frequency'] = 20e6
        df.loc[df['Frequency'] == 'WWV25',   'Frequency'] = 25e6
        df.loc[df['Frequency'] == 'CHU3',    'Frequency'] = 3330e3
        df.loc[df['Frequency'] == 'CHU7',    'Frequency'] = 7850e3
        df.loc[df['Frequency'] == 'CHU14',   'Frequency'] = 14.67e6
        df.loc[df['Frequency'] == 'Unknown', 'Frequency'] = 0

        # Sort by Datetime
        df = df.sort_values(['Datetime','Frequency','Node']).copy()

        # Save dataframe to object
        self.df_unfiltered = df

        df = self.filter(nodes=nodes,G=G,freq=freq,sTime=sTime,eTime=eTime)

    def filter(self,nodes=None,G=None,freq=None,sTime=None,eTime=None):
        """
        Filter data by parameters. The filtered results will be returned
        and stored in self.df. Unfiltered results will always be available
        in self.df_unfiltered.

        Parameters
        ----------
        nodes:  node numbers (int or list of ints)
        G:      type of receiving station (string or list of strings)
		        G – Grape (Low Cost Platform)
		    	1 – Generation 1 (has 1 receiver)
		    	2 – Generation 2 (has 4 receivers)
		        T – Tangerine (High Performance Platform)
		    	1 – Generation 1
		     	2 – Generation 2
		        S – Standard / Commercial (Amateur) Hardware (details in Metadata)
		        U – User Custom Hardware (details in Metadata)
		        N – No radio collectiopn – instrument(s) only – defaults to N0
        freq:   frequency in Hz (float or list of floats)
        sTime:  UTC starting time of observations (datetime object)
        eTime:  UTC ending time of observations (datetime object)

        """

        df = self.df_unfiltered.copy()

        if nodes is not None:
            nodes   = gl.get_iterable(nodes)
            tf      = df['Node'].isin(nodes)
            df      = df[tf].copy()

        if G is not None:
            G       = gl.get_iterable(G)
            tf      = df['G'].isin(G)
            df      = df[tf].copy()

        if freq is not None:
            freq    = gl.get_iterable(freq)
            tf      = df['Frequency'].isin(freq)
            df      = df[tf].copy()

        if sTime is not None:
            # Return files that start a day early,
            # otherwise you may not get all of the files you need.
            sTime  -= datetime.timedelta(days=1)

            tf      = df['Datetime'] >= sTime
            df      = df[tf].copy()

        if eTime is not None:
            # Return files that end a day after eTime,
            # otherwise you may not get all of the files you need.
            eTime  += datetime.timedelta(days=1)

            tf      = df['Datetime'] < eTime
            df      = df[tf].copy()

        self.df = df

        # List of logged nodes during the period of interest, for sorting:
        logged_nodes = df["Node"].unique().tolist()
        logged_nodes.sort()
        self.logged_nodes = logged_nodes

        return df

    def get_nodes(self):
        """
        Return a list of all of the unique node numbers in the inventory.
        """
        nodes = self.df['Node'].unique().tolist()
        return nodes

    def plot_inventory(self,html_out='inventory.html'):
        """
        Generate a plot of the data inventory using plotly.

        html_out: Filename of html-version of data inventory plot.
        """
        # We can create a Data Inventory (Gantt chart) showing when different stations were active.
        invt         = self.df.copy()
        logged_nodes = self.logged_nodes

        invt.set_index('Node')
        invt = invt.drop(columns=['G', 'Grid Square'])
        invt['EndTime'] = invt['Datetime']+ datetime.timedelta(days=1) # create an end
        invt['Filename'].str.strip('FRQ_')

        fig = px.timeline(invt, x_start="Datetime", x_end="EndTime", y="Node", color="Frequency", category_orders={"Node": logged_nodes})
        fig.update_yaxes(type='category')
        fig.update_annotations(text = "Filename", clicktoshow='on')

        fig.update_layout({
            'plot_bgcolor': 'rgba(0, 0, 0, 0)',
            'paper_bgcolor': 'rgba(0, 0, 0, 0)',
            'font_size': mpl.rcParams['font.size']
        })

        fig.show()

        if html_out:
            fig.write_html(html_out, include_plotlyjs="cdn")

class GrapeNodes(object):
    def __init__(self,fpath='nodelist.csv',logged_nodes=None):
        """
        Create an object with information about known Grape1 nodes from a nodelist.csv.

        fpath:        path to nodelist.csv
        logged_nodes: list of nodes we have data for. This can be obtained from DataInventory.logged_nodes.
        """
        # Read in node list:
        nodes_df = pd.read_csv(fpath)
        nodes_df = nodes_df.rename(columns={'Node #': 'Node_Number'})
        nodes_df = nodes_df.set_index('Node_Number')

        self.nodes_df = nodes_df

        if logged_nodes:
            nodes_df = self.update_status(logged_nodes)

    def update_status(self,logged_nodes):
        """
        Updates the nodes_df dataframe with with information regarding which nodes we have data from.

        logged_nodes: list of nodes we have data for. This can be obtained from DataInventory.logged_nodes.
        """
        nodes_df = self.nodes_df

        nodes_df['Status'] = np.where((nodes_df.index.isin(logged_nodes)), "Data logged", "No data logged")
        self.nodes_df = nodes_df
        return nodes_df

    def status_table(self):
        """
        Returns the nodes_df data frame with nodes that we have data for highlighted in green.
        """
        # Highlight rows in green for stations that we have data from.
        nodes_df = self.nodes_df.copy()
        color    = (nodes_df['Status'] == 'Data logged').map({True: 'background-color: palegreen', False: ''})
        nodes_df = nodes_df.style.apply(lambda s: color)
        return nodes_df

    def plot_map(self,color="Status",projection='albers usa',
            width=1200,height=900):
        """
        Use plotly to generate a map of Grape1 nodes.
        """
        nodes_df = self.nodes_df
        # Map nodes:
        fig = px.scatter_geo(nodes_df, "Latitude", "Longitude",
                             color=color, # which column to use to set the color of markers
                             hover_name=nodes_df["Callsign"], # column added to hover information
                             projection = projection,
                             width = width, height = height,
                             )
        fig.show()

class Filter(object):
    def __init__(self,N=6,Tc_min = 3.3333,btype='low',fs=1.):
        """
        Generate a digital filter that can be applied to the data.

        This routine uses the scipy.signal.butter Butterworth filter.

        Parameters
        ----------
        N:      Filter order.
        Tc_min: Cuttoff in minutes. Scalar value for 'low' and 'high'
                filter btypes; 2-element iterable for 'bandpass' and
                'bandstop' filter btypes.
        fs:     Sampling frequency of the digital system in samples/sec.
        """
        Wn     = (1./(np.array(Tc_min)*60.))

        if np.size(Wn) == 2:
            Wn = Wn[::-1]

        #Wn    = (1000, 5000)    # 3 dB Cutoff Frequency in Hz
               # Choose 'bandpass' or 'bandstop'

        b, a = signal.butter(N, Wn, btype, fs=fs)

        self.fs = fs
        self.Wn = Wn
        self.a  = a
        self.b  = b

    def filter_data(self,data):
        """
        Apply the filter using scipy.signal.filtfilt to data.

        data: Vector of data to be filtered.
        """
        try:
            filtered = signal.filtfilt(self.b,self.a,data)
        except Exception as err:
            print('Filter error... returning NaNs.')
            print('   Error: {!s}'.format(err))
            filtered = data*np.nan

        return filtered

    def plotResponse(self):
        """
        Plot the magnitude and phase response of the filter.
        """
        fs = self.fs
        Wn = self.Wn
        a  = self.a
        b  = self.b

        w, h = signal.freqz(b, a,worN=2**16)
        f = (fs/2)*(w/(np.pi))

        plt.figure(figsize=(12,8))
        plt.subplot(211)
        plt.plot(f, 20 * np.log10(abs(h)))
        plt.xscale('log')
        plt.title('Butterworth Filter Frequency Response')
        plt.xlabel('Frequency [Hz]')
        plt.ylabel('Amplitude [dB]')
        plt.grid(which='both', axis='both')
        if np.size(Wn) == 1:
            plt.axvline(Wn, color='green')    # cutoff frequency
        else:
            plt.axvline(Wn[0], color='green') # cutoff frequency
            plt.axvline(Wn[1], color='green') # cutoff frequency

        plt.ylim(-30,0)

        plt.subplot(212)
        plt.plot(f, np.unwrap(np.angle(h)))
        plt.xscale('log')
        plt.title('Butterworth Filter Phase Response')
        plt.xlabel('Frequency [Hz]')
        plt.ylabel('Phase [rad]')
        plt.grid(which='both', axis='both')
        if np.size(Wn) == 1:
            plt.axvline(Wn, color='green')    # cutoff frequency
        else:
            plt.axvline(Wn[0], color='green') # cutoff frequency
            plt.axvline(Wn[1], color='green') # cutoff frequency

        plt.tight_layout()

class Grape1Data(object):
    def __init__(self,node=None,freq=None,sTime=None,eTime=None,data_path='data',
                 lat=None,lon=None,call_sign=None,
                 solar_lat=None,solar_lon=None,
                 inventory=None,grape_nodes=None,
                 data=None,meta=None):

        if data is None and meta is None:
            self.__load_raw(node,freq,sTime,eTime=eTime,data_path=data_path,
                     lat=lat,lon=lon,call_sign=call_sign,solar_lat=solar_lat,solar_lon=solar_lon,
                     inventory=inventory,grape_nodes=grape_nodes)
        else:
            self.data = data
            self.meta = meta

    def __load_raw(self,node,freq,sTime,eTime,data_path,
                 lat,lon,call_sign,solar_lat,solar_lon,inventory,grape_nodes):

        if inventory is None:
            inventory = DataInventory(data_path=data_path)

        # Make temporary copy of dataframe.
        dft   = inventory.df.copy()

        # Select rows with selected frequency
        tf    = dft['Frequency'] == freq
        dft   = dft[tf].copy()

        # Select rows matching node number.
        tf    = dft['Node'] == node
        dft   = dft[tf].copy()

        if sTime is None:
            sTime = min(dft['Datetime'])

        if eTime is None:
            eTime = max(dft['Datetime'])

        # Expand time range of loading files, or you might miss
        # files you need. Strict time limits are applied later in this
        # routine after the data has been loaded.
        sTime_load  = sTime - datetime.timedelta(days=1)
        eTime_load  = eTime + datetime.timedelta(days=1)

        # Select rows matching time range.
        tf    = np.logical_and(dft['Datetime'] >= sTime_load, dft['Datetime'] < eTime_load)
        dft   = dft[tf].copy()
        dft   = dft.sort_values('Datetime')

        # Load data from every data file available for node/frequency/date range.
        df_raw = []
        for rinx,row in tqdm(dft.iterrows(),total=len(dft),dynamic_ncols=True,desc='Loading Raw Data'):
            fname = row['Filename']
            fpath = os.path.join(data_path,fname)

            df_load = pd.read_csv(fpath, comment = '#', parse_dates=[0])
            if len(df_load) == 0: continue

            # Remove the center frequency offset from the frequency column.
            df_load['Freq'] = df_load['Freq']-freq
            df_raw.append(df_load)

        df_raw  = pd.concat(df_raw,ignore_index=True)
        df_raw  = df_raw.sort_values('UTC')

        # Enforce sTime/eTime
        tf      = np.logical_and(df_raw['UTC'] >= sTime, df_raw['UTC'] < eTime)
        df_raw  = df_raw[tf].copy()

        # Generate a label for each Node
        if (call_sign is None) and (grape_nodes is not None):
            call_sign = grape_nodes.nodes_df.loc[node,'Callsign']
        else:
            call_sign = ''

        # Get latitude and longitude
        if (lat is None) and (grape_nodes is not None):
            lat = grape_nodes.nodes_df.loc[node,'Latitude']

        if (lon is None) and (grape_nodes is not None):
            lon = grape_nodes.nodes_df.loc[node,'Longitude']

        if solar_lat is None:
            solar_lat = lat

        if solar_lon is None:
            solar_lon = lon

        # Store data into dictionaries.
        data = {}
        data['raw']          = {}
        data['raw']['df']    = df_raw
        data['raw']['label'] = 'Raw Data'
        self.data = data

        label     = '{:0.1f} MHz N{:07d} {!s}'.format(freq/1e6,node,call_sign)

        meta = {}
        meta['label']       = label
        meta['freq']        = freq
        meta['node']        = node
        meta['call_sign']   = call_sign
        meta['lat']         = lat
        meta['lon']         = lon
        meta['solar_lat']   = solar_lat
        meta['solar_lon']   = solar_lon
        self.meta      = meta

    def process_data(self,profile='standard'):
        tic_0 = datetime.datetime.now()
        print('Processing data using "{!s}" profile...'.format(profile))
        print('')
        if profile == 'standard':
            print('Computing Solar Local Time on raw data...')
            tic = datetime.datetime.now()
            self.calculate_solar_time('raw')
            toc = datetime.datetime.now()
            print('  Solar Time Computation Time: {!s}'.format(toc-tic))

            resample_rate = datetime.timedelta(seconds=1)
            filter_order  = 6
            Tc_min        = 3.3333
            btype         = 'low'

            print('Resampling data with {!s} second cadence...'.format(resample_rate.total_seconds()))
            tic = datetime.datetime.now()
            self.resample_data(resample_rate=resample_rate,
                              data_set_in='raw',data_set_out='resampled')
            toc = datetime.datetime.now()
            print('  Resampling Time: {!s}'.format(toc-tic))

            print('Computing Solar Local Time on resampled...')
            tic = datetime.datetime.now()
            self.calculate_solar_time('resampled')
            toc = datetime.datetime.now()
            print('  Solar Time Computation Time: {!s}'.format(toc-tic))

            # Convert Vpk to Power_dB
            print('dB Conversion')
            tic = datetime.datetime.now()
            self.data['resampled']['df']['Power_dB'] = 20*np.log10( self.data['resampled']['df']['Vpk'])
            toc = datetime.datetime.now()
            print('  dB Conversion Time: {!s}'.format(toc-tic))

            print('Filtering data with {!s} minute low-pass Butterworth filter...'.format(Tc_min))
            tic = datetime.datetime.now()
            self.filter_data(N=filter_order,Tc_min=Tc_min,btype=btype)
            toc = datetime.datetime.now()
            print('  Filtering Time: {!s}'.format(toc-tic))

        elif profile == '5min_mean':
            data_set_in = 'raw'
            data_set    = 'resampled'
            xkeys       = ['LMT','UTC']
            params      = ['Freq','Power_dB']

            resample_rate = datetime.timedelta(minutes=5)
            print('Resampling data with {!s} minute cadence...'.format(resample_rate.total_seconds()/60.))
            tic = datetime.datetime.now()
            self.resample_data(resample_rate=resample_rate,method='mean',
                              data_set_in=data_set_in,data_set_out=data_set)
            toc = datetime.datetime.now()
            print('  Resampling Time: {!s}'.format(toc-tic))
            print()

            print('Computing Solar Local Time on resampled...')
            tic = datetime.datetime.now()
            self.calculate_solar_time(data_set)
            toc = datetime.datetime.now()
            print('  Solar Time Computation Time: {!s}'.format(toc-tic))
            print()

            # Convert Vpk to Power_dB
            print('dB Conversion')
            tic = datetime.datetime.now()
            self.data[data_set]['df']['Power_dB'] = 20*np.log10( self.data[data_set]['df']['Vpk'])
            toc = datetime.datetime.now()
            print('  dB Conversion Time: {!s}'.format(toc-tic))
            print()

            for xkey in xkeys:
                for param in params:
                    print('Processing: {!s} - {!s}'.format(xkey,param))
                    print('Compute Time-Date-Parameter (TDP) Array')
                    tic = datetime.datetime.now()
                    tdp = self.calculate_timeDateParameter_array(data_set,param,xkey=xkey)
                    toc = datetime.datetime.now()
                    print('  Time-Date-Parameter Time: {!s}'.format(toc-tic))
                    print()

                    tdp_key = self.get_tdp_key(param,xkey)
                    self.data[data_set][tdp_key] = tdp

        toc_0 = datetime.datetime.now()
        print('')
        print('Total Processing Time: {!s}'.format(toc_0-tic_0))

    def resample_data(self,resample_rate,on='UTC',method='linear_interp',
                          data_set_in='raw',data_set_out='resampled'):

        df   = self.data[data_set_in]['df'].copy()

        if len(df) == 0:
            rs_df = df.copy()
        else:
            # Create the list of datetimes that we want to resample to.
            # Find the start and end times of the array.
            sTime = df['UTC'].min()
            eTime = df['UTC'].max()

            tzinfo= sTime.tzinfo

            # Break
            sYr  = sTime.year
            sMon = sTime.month
            sDy  = sTime.day
            sHr  = sTime.hour
            sMin = sTime.minute
            sSec = sTime.second
            resample_sTime = datetime.datetime(sYr,sMon,sDy,sHr,sMin,sSec,tzinfo=tzinfo)

            eYr  = eTime.year
            eMon = eTime.month
            eDy  = eTime.day
            eHr  = eTime.hour
            eMin = eTime.minute
            eSec = eTime.second
            resample_eTime = datetime.datetime(eYr,eMon,eDy,eHr,eMin,eSec,tzinfo=tzinfo)

            # Remove LMT column if it exists because it cannot be resampled.
            if 'LMT' in df.keys():
                df = df.drop('LMT',axis=1)

            cols        = df.keys()
            df          = df.set_index(on) # Need to make UTC column index for interpolation to work.
            df          = df.drop_duplicates()

            df          = df[~df.index.duplicated(keep='first')] # Make sure there are no duplicated indices.

            rs_df       = df.resample(resample_rate,origin=resample_sTime)
            if method == 'mean':
                rs_df = rs_df.mean()
            else:
                rs_df = rs_df.interpolate(method='linear')

            rs_df       = rs_df.copy()
            rs_df[on]   = rs_df.index
            rs_df.index = np.arange(len(rs_df))

            # Put Columns back in original order.
            rs_df       = rs_df[cols].copy()

        tmp          = {}
        tmp['df']    = rs_df
        tmp['label'] = 'Resampled Data (dt = {!s} s)'.format(resample_rate.total_seconds())
        tmp['Ts']    = resample_rate.total_seconds()
        self.data[data_set_out] = tmp

    def filter_data(self,N,Tc_min,btype,
                    data_set_in='resampled',data_set_out='filtered',
                    params=['Freq','Vpk','Power_dB']):

        df     = self.data[data_set_in]['df'].copy()

        # Get sample rate of data set.
        Ts     = self.data[data_set_in].get('Ts')
        if Ts is None:
            Ts_arr = np.unique(np.diff(df['UTC']))
            if len(Ts_arr) != 1:
                raise Exception("{!s} is not evenly sampled. Cannot apply filter.".format(data_set_in))
            Ts   = (Ts_arr[0]).total_seconds()

        # Convert sample rate to sampling frequency.
        fs   = 1./Ts

        filt = Filter(N=N,Tc_min=Tc_min,btype=btype,fs=fs)

        for param in params:
            if param not in df.keys():
                continue
            df[param] = filt.filter_data(df[param])

        tmp          = {}
        tmp['df']    = df
        tmp['label'] = 'Butterworth Filtered Data\n(N={!s}, Tc={!s} min, Type: {!s})'.format(N, Tc_min, btype)
        self.data[data_set_out] = tmp

    def calculate_solar_time(self,data_set,solar_lon=None):
        if solar_lon is None:
            solar_lon = self.meta.get('solar_lon')
        else:
            self.meta['solar_lon'] = solar_lon

        df = self.data.get(data_set)['df']
        df['LMT'] = df['UTC'].progress_apply(solar.solar_time,lon=solar_lon)

        # Set columns so UTC and LMT lead.
        keys = list(df.keys())
        keys.remove('UTC')
        keys.remove('LMT')
        keys = ['UTC','LMT'] + keys
        df   = df[keys]
        self.data[data_set]['df'] = df

    def calculate_timeDateParameter_array(self,data_set,param='Freq',xkey='LMT'):
        df          = self.data[data_set]['df']
        time_vec    = df[xkey]

        # Calculate bin size.
        dt_hr       = (time_vec.diff().mean().total_seconds())/3600.
        hour_bins   = np.arange(0,24+dt_hr,dt_hr)

        print('Splitting {!s} into dates...'.format(xkey))
        dates       = time_vec.progress_apply(gl.datetime2date)
        date_bins   = list(set(dates))
        date_bins.sort()
        date_bins   = np.array(date_bins)

        print('Splitting {!s} into decimal hours...'.format(xkey))
        hours       = time_vec.progress_apply(gl.decimal_hours)

        # Rearrange Data into a 2D Array
        print('Filling timeDateParameter Array...')
        tdp_arr = np.zeros( (len(date_bins), len(hour_bins)) )*np.nan
        for rinx, row in tqdm(df.iterrows(),total=len(df),dynamic_ncols=True):
            val         = row[param]
            date_inx    = np.where(dates.loc[rinx] == date_bins)[0][0]
            t_bin_inx   = np.digitize(hours.loc[rinx],hour_bins)

            tdp_arr[date_inx,t_bin_inx] = val

        # Put array into xarray.
        xr_xkey = '{!s}_Date'.format(xkey)
        xr_ykey = '{!s}_Hour'.format(xkey)

        crds    = {}
        crds[xr_xkey]	= date_bins
        crds[xr_ykey]   = hour_bins
        tdp_da          = xr.DataArray(tdp_arr,crds,dims=[xr_xkey,xr_ykey])

        tdp_key         = self.get_tdp_key(param,xkey)

        # Save back to object
        self.data[data_set][tdp_key] = tdp_da

        return tdp_da

    def get_tdp_key(self,param,xkey):
        tdp_key = 'tdp_{!s}_{!s}'.format(param,xkey)
        return tdp_key

    def show_datasets(self):
        keys        = []
        datasets    = []
        for key,dct in self.data.items():
            keys.append(key)

            tmp  = {}
            tmp['label'] = dct.get('label')

            datasets.append(tmp)

        datasets = pd.DataFrame(datasets,index=keys)
        return datasets

    def plot_timeSeries(self,data_sets=['raw'],
                        sTime=None,eTime=None,
                        xkey='UTC',params=['Freq','Vpk','Power_dB'],
                        ylims = {},
                        plot_kws = [{'ls':'','marker':'.'},{}],
                        overlayTerminator=True,
                        solar_lat=None,solar_lon=None,
                        fig_width=15,panel_height=6):

        data_sets = gl.get_iterable(data_sets)

        df      = self.data[data_sets[0]]['df']
        if sTime is None:
            sTime = min(df[xkey])

        if eTime is None:
            eTime = max(df[xkey])

        # Make sure we have the requested parameters.
        params = gl.get_iterable(params)
        params_good = []
        for param in params:
            if param in df.keys():
                params_good.append(param)
        params = params_good

        # Start plotting
        ncols   = 1
        nrows   = len(params)
        figsize = (fig_width, nrows*panel_height)

        fig = plt.figure(figsize=figsize)
        axs = []
        for plt_inx,param in enumerate(params):
            ax  = fig.add_subplot(nrows,ncols,plt_inx+1)
            axs.append(ax)

            if overlayTerminator:
                solar_lat = self.meta.get('solar_lat',solar_lat)
                solar_lon = self.meta.get('solar_lon',solar_lon)
                if (solar_lat is not None) and (solar_lon is not None):
                    solar.add_terminator(sTime,eTime,solar_lat,solar_lon,ax,xkey=xkey)
                else:
                    print('Error: Need to provide solar_lat and solar_lon to overlay solar terminator.')

            ax.set_xlim(sTime,eTime)
            if plt_inx != nrows-1:
                ax.set_xticklabels('')
            else:
                fig.autofmt_xdate()
                xprmd  = prm_dict.get(xkey,{})
                xlabel = xprmd.get('label',xkey)
                ax.set_xlabel(xlabel)

            yprmd  = prm_dict.get(param,{})
            ylabel = yprmd.get('label',yprmd)
            ax.set_ylabel(ylabel)
            ylim = ylims.get(param,None)
            if ylim is not None:
                ax.set_ylim(ylim)

            if plt_inx == 0:
                ax.set_title(self.meta.get('label',''))
            ax.set_title('({!s})'.format(letters[plt_inx]),loc='left')

        for ds_inx,data_set in enumerate(data_sets):
            for plt_inx,param in enumerate(params):
                ax = axs[plt_inx]

                df  = self.data[data_set]['df']
                xx  = df[xkey]
                yy  = df[param]

                label   = self.data[data_set].get('label','')
                plot_kw = plot_kws[ds_inx]
                ax.plot(xx,yy,label=label,**plot_kw)

        for plt_inx,param in enumerate(params):
            ax = axs[plt_inx]
            ax.legend(loc='upper right',fontsize='small')

        fig.tight_layout()

        return {'fig':fig}

    def plot_timeDateParameter(self,data_set,params=['Freq','Power_dB'],xkey='LMT',
            fig_width=15,panel_height=6):

        # Start plotting
        ncols   = 1
        nrows   = len(params)
        figsize = (fig_width, nrows*panel_height)

        fig = plt.figure(figsize=figsize)
        axs = []
        for plt_inx,param in enumerate(params):
            tdp_key = self.get_tdp_key(param,xkey)
            tdp = self.data[data_set][tdp_key]

            ax  = fig.add_subplot(nrows,ncols,plt_inx+1)
            axs.append(ax)

            xr_xkey = '{!s}_Date'.format(xkey)
            xprmd   = prm_dict.get(xr_xkey)
            xlabel  = xprmd.get('label')

            xr_ykey = '{!s}_Hour'.format(xkey)
            yprmd   = prm_dict.get(xr_ykey)
            ylabel  = yprmd.get('label')

            prmd = prm_dict.get(param)
            vmin = prmd.get('vmin')
            vmax = prmd.get('vmax')
            cmap = prmd.get('cmap')
            plbl = prmd.get('label')

            cbar_kwargs = {}
            cbar_kwargs['label'] = plbl

            ret  = tdp.plot.pcolormesh(xr_xkey,xr_ykey,vmin=vmin,vmax=vmax,cmap=cmap,
                    cbar_kwargs=cbar_kwargs,ax=ax)
            ax.set_ylabel(ylabel)

            if plt_inx != nrows-1:
#                ax.set_xticklabels('')
                ax.set_xlabel('')
            else:
                ax.set_xlabel(xlabel)

            if plt_inx == 0:
                ax.set_title(self.meta.get('label',''))
            ax.set_title('({!s})'.format(letters[plt_inx]),loc='left')

            fig.tight_layout()

        return {'fig':fig}

class GrapeMultiplot(object):
    def __init__(self,gds):
        """
        gds: List of Grape1Data objects.
        """

        self.gds = gds

    def multiplot(self,data_set,params=['Freq','Power_dB'],
                    xkey='UTC',
                    sTime=None,eTime=None,
                    ylims=None,
                    color_dct=None,legend=True,
                    solar_lat=None,solar_lon=None,
                    events=None, event_fontdict = {'size':20,'weight':'bold'},
                    plot_GOES=False,GOES_sat_nr=17,GOES_data_dir='data/goes',
                    fig_width=22,panel_height=8):
        """
        Plot a time series with traces from multiple instruments overlaid on the same plot.

        Parameters
        ----------
        data_set:   Name of data set to plot (string)
        params:     List of parameter keys to plot. A new subplot will be created for each param.
        xkey:       X-axis parameter.
                        'UTC' for Coordinated Universal Time
                        'LMT' for Local Mean Time (solar time)
        sTime:      Start time of plot (UTC or LMT datetime object)
        eTime:      End time of plot (UTC or LMT datetime object)
        ylims:      None or dictionary with ylim for each parameter.
                    Example:
                      {'Freq':(-5,5)}
        color_dct:  Dictionary indicating how to color the traces.
                    Example:
                      {'ckey':'lon'} will color the traces according to the lon parameter
                            using the default colormap (viridis) and vmax/min parameters.

                      {'ckey':'lat','cmap':'jet','vmin':-90,'vmax':90} will color the traces
                            according to the lat parameter using the jet colormap with
                            vmin=-90 and vmax=90.
        legend:     Plot legend with name of each receiving station.
        solar_lat:  Latitude for computing sunrise/sunset times.
        solar_lon:  Longitude for computing sunrise/sunset times.

        """

        data = self.gds

        # Sort data objects by specified parameter and set color.
        if color_dct is not None:
            ckey = color_dct.get('ckey')

            # Sort data by ckey.
            vals = [x.meta[ckey] for x in data]
            srt  = np.argsort(vals)[::-1]
            data = [data[x] for x in srt]

            cmap = color_dct.get('cmap','viridis')
            if len(vals) > 0:
                vmin = color_dct.get('vmin',np.min(vals))
                vmax = color_dct.get('vmax',np.max(vals))
            else:
                vmin = -10.
                vmax =  10.

            if vmin == vmax:
                vmin = vmin - 10.
                vmax = vmax + 10.

            norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax)
            mpbl = mpl.cm.ScalarMappable(norm=norm, cmap=cmap)

        # Determine sTime and eTime if not specified.
        if sTime is None or eTime is None:
            xx_xtrm   = [] # Keep track of time extrema.
            for gd in data:
                df_data = gd.data[data_set]['df']
                if len(df_data) == 0:
                    continue

                xx      = df_data[xkey]
                xx_xtrm.append(np.min(xx))
                xx_xtrm.append(np.max(xx))

            if sTime is None:
                sTime = min(xx_xtrm)

            if eTime is None:
                eTime = max(xx_xtrm)

        ncols = 1
        nrows = len(params)
        if plot_GOES:
            nrows += 1

        figsize = (fig_width, nrows*panel_height)
        fig     = plt.figure(figsize=figsize)

        axs = []
        plt_inx = 0
        if plot_GOES:
            plt_inx += 1
            ax   = fig.add_subplot(nrows, ncols, plt_inx)
            ax.set_title('({!s})'.format(letters[plt_inx]),loc='left')
            axs.append(ax)

            goes_data = goes.read_goes(sTime,eTime,sat_nr=GOES_sat_nr,data_dir=GOES_data_dir)
            goes.goes_plot(goes_data,sTime,eTime,ax=ax)
            ax.set_title('NOAA GOES-{:02d}'.format(GOES_sat_nr),loc='right')

            if plt_inx != nrows-1:
                ax.set_xticklabels('')

            if events is not None:
                trans       = mpl.transforms.blended_transform_factory(ax.transData,ax.transAxes)
                for event in events:
                    evt_dtime   = event.get('datetime')
                    evt_label   = event.get('label')
                    evt_color   = event.get('color','0.4')

                    ax.axvline(evt_dtime,lw=1,ls='--',color=evt_color)
                    if evt_label is not None:
                        ax.text(evt_dtime,0.01,evt_label,transform=trans,
                                rotation=90,fontdict=event_fontdict,color=evt_color,
                                va='bottom',ha='right')

        params = gl.get_iterable(params)
        for prm_inx,(param) in enumerate(params):
            plt_inx += 1
            ax   = fig.add_subplot(nrows, ncols, plt_inx)
            axs.append(ax)

            abs_maxes = [] # Keep track of absolute value maxima
            for gd in data:
                df_data = gd.data[data_set]['df']
                if len(df_data) == 0:
                    continue

                label   = gd.meta['label']

                plt_kw  = {}

                xx      = df_data[xkey]
                yy      = df_data[param]

                if not np.all(np.isfinite(yy)):
                    continue

                abs_maxes.append(np.nanmax(np.abs(yy)))
                if color_dct is not None:
                    val     = gd.meta[ckey]
                    plt_kw['color'] = mpbl.cmap(mpbl.norm(val))

                ax.plot(xx,yy,label=label,**plt_kw)

            ax.set_title('({!s})'.format(letters[plt_inx]),loc='left')

            prmd    = prm_dict.get(param,{})
            rtitle  = prmd.get('title')
            if rtitle:
                ax.set_title(rtitle,loc='right')

            ylbl    = prmd.get('label',param)
            ax.set_ylabel(ylbl)

            legend_bbox_to_anchor = (1.04,1)
            if color_dct is not None:
                cb_prmd = prm_dict.get(ckey,{})
                clabel  = cb_prmd.get('label',ckey)
                fig.colorbar(mpbl,ax=ax,label=clabel)
                legend_bbox_to_anchor = (1.2,1)

            if legend:
                ax.legend(bbox_to_anchor=legend_bbox_to_anchor, loc="upper left", borderaxespad=0)

            if len(xx_xtrm) == 0:
                ax.text(0.5,0.5,'No Data',ha='center',transform=ax.transAxes,fontsize=36)
                continue

            ax.set_xlim(sTime,eTime)

            if param == 'Freq':
                abs_max = np.max(abs_maxes)
                ax.set_ylim(-abs_max*1.05,abs_max*1.05)

            # Set ylim from ylims keyword.
            if ylims is not None:
                ylim = ylims.get(param)
                if ylim is not None:
                    ax.set_ylim(ylim)

            if plt_inx != nrows:
                ax.set_xticklabels('')
            else:
                xprmd   = prm_dict.get(xkey,{})
                xlbl    = xprmd.get('label',xkey)
                ax.set_xlabel(xlbl)

            if events is not None:
                trans       = mpl.transforms.blended_transform_factory(ax.transData,ax.transAxes)
                for event in events:
                    evt_dtime   = event.get('datetime')
                    evt_label   = event.get('label')
                    evt_color   = event.get('color','0.4')

                    ax.axvline(evt_dtime,lw=1,ls='--',color=evt_color)
                    if evt_label is not None:
                        ax.text(evt_dtime,0.01,evt_label,transform=trans,
                                rotation=90,fontdict=event_fontdict,color=evt_color,
                                va='bottom',ha='right')

            # Add solar terminator
            if (solar_lat is not None) and (solar_lon is not None):
                solar.add_terminator(sTime,eTime,solar_lat,solar_lon,ax,xkey=xkey)

        date_str = sTime.strftime('%Y %b %d %H:%M UT') + ' - ' + eTime.strftime('%Y %b %d %H:%M UT')
        title = []
#        title.append('HamSCI Grape PSWS Observations')
        title.append(date_str)
        axs[0].text(0.5,1.1,'\n'.join(title),ha='center',fontsize=24,
                fontweight='bold',transform=axs[0].transAxes)

        fig.tight_layout()
        if plot_GOES:
            gl.adjust_axes(axs[0],axs[-1])

        return {'fig':fig,'axs':axs}