import numpy as np
import torch
from torch.distributions import OneHotCategorical
from offpolicy.algorithms.r_maddpg.algorithm.r_actor_critic import R_MADDPG_Actor, R_MADDPG_Critic
from offpolicy.algorithms.r_matd3.algorithm.r_actor_critic import R_MATD3_Actor, R_MATD3_Critic
from offpolicy.utils.util import is_discrete, is_multidiscrete, get_dim_from_space, DecayThenFlatSchedule, soft_update, hard_update, \
    gumbel_softmax, onehot_from_logits, gaussian_noise, avail_choose, to_numpy
from offpolicy.algorithms.base.recurrent_policy import RecurrentPolicy

class R_MADDPGPolicy(RecurrentPolicy):
    """
    Recurrent MADDPG/MATD3 Policy Class to wrap actor/critic and compute actions. See parent class for details.
    :param config: (dict) contains information about hyperparameters and algorithm configuration
    :param policy_config: (dict) contains information specific to the policy (obs dim, act dim, etc)
    :param target_noise: (int) std of target smoothing noise to add for MATD3 (applies only for continuous actions)
    :param td3: (bool) whether to use MATD3 or MADDPG.
    :param train: (bool) whether the policy will be trained.
    """
    def __init__(self, config, policy_config, target_noise=None, td3=False, train=True):
        self.config = config
        self.device = config['device']
        self.args = self.config["args"]
        self.tau = self.args.tau
        self.lr = self.args.lr
        self.opti_eps = self.args.opti_eps
        self.weight_decay = self.args.weight_decay
        self.prev_act_inp = self.args.prev_act_inp

        self.central_obs_dim, self.central_act_dim = policy_config["cent_obs_dim"], policy_config["cent_act_dim"]
        self.obs_space = policy_config["obs_space"]
        self.obs_dim = get_dim_from_space(self.obs_space)
        self.act_space = policy_config["act_space"]
        self.act_dim = get_dim_from_space(self.act_space)
        self.output_dim = sum(self.act_dim) if isinstance(self.act_dim, np.ndarray) else self.act_dim
        self.hidden_size = self.args.hidden_size
        self.discrete = is_discrete(self.act_space)
        self.multidiscrete = is_multidiscrete(self.act_space)

        actor_class = R_MATD3_Actor if td3 else R_MADDPG_Actor
        critic_class = R_MATD3_Critic if td3 else R_MADDPG_Critic
        self.actor = actor_class(self.args, self.obs_dim, self.act_dim, self.device, take_prev_action=self.prev_act_inp)
        self.critic = critic_class(self.args, self.central_obs_dim, self.central_act_dim, self.device)

        self.target_actor = actor_class(self.args, self.obs_dim, self.act_dim, self.device, take_prev_action=self.prev_act_inp)
        self.target_critic = critic_class(self.args, self.central_obs_dim, self.central_act_dim, self.device)

        # sync the target weights
        self.target_actor.load_state_dict(self.actor.state_dict())
        self.target_critic.load_state_dict(self.critic.state_dict())

        if train:
            self.actor_optimizer = torch.optim.Adam(self.actor.parameters(), lr=self.lr, eps=self.opti_eps, weight_decay=self.weight_decay)
            self.critic_optimizer = torch.optim.Adam(self.critic.parameters(), lr=self.lr, eps=self.opti_eps, weight_decay=self.weight_decay)

            if self.discrete:
                # eps greedy exploration
                self.exploration = DecayThenFlatSchedule(self.args.epsilon_start, self.args.epsilon_finish,
                                                         self.args.epsilon_anneal_time, decay="linear")
        self.target_noise = target_noise

    def get_actions(self, obs, prev_actions, rnn_states, available_actions=None, t_env=None, explore=False, use_target=False, use_gumbel=False):
        """
        See parent class.
        :param use_target: (bool) whether to use the target actor or live actor.
        :param use_gumbel: (bool) whether to apply gumbel softmax on the actions.
        """
        assert prev_actions is None or len(obs.shape) == len(prev_actions.shape)
        # obs is either an array of shape (batch_size, obs_dim) or (seq_len, batch_size, obs_dim)
        if len(obs.shape) == 2:
            batch_size = obs.shape[0]
            no_sequence = True
        else:
            batch_size = obs.shape[1]
            no_sequence = False

        eps = None
        if use_target:
            actor_out, new_rnn_states = self.target_actor(obs, prev_actions, rnn_states)
        else:
            actor_out, new_rnn_states = self.actor(obs, prev_actions, rnn_states)

        if self.discrete:
            if self.multidiscrete:
                if use_gumbel or (use_target and self.target_noise is not None):
                    onehot_actions = list(map(lambda a: gumbel_softmax(a, hard=True, device=self.device), actor_out))
                    actions = torch.cat(onehot_actions, dim=-1)
                elif explore:
                    onehot_actions = list(map(lambda a: gumbel_softmax(a, hard=True, device=self.device), actor_out))
                    onehot_actions = torch.cat(onehot_actions, dim=-1)
                    assert no_sequence, "Doesn't make sense to do exploration on a sequence!"
                    # eps greedy exploration
                    eps = self.exploration.eval(t_env)
                    rand_numbers = np.random.rand(batch_size, 1)
                    take_random = (rand_numbers < eps).astype(int).reshape(-1, 1)
                    # random actions sample uniformly from action space
                    random_actions = [OneHotCategorical(logits=torch.ones(batch_size, self.act_dim[i])).sample() for i in range(len(self.act_dim))]
                    random_actions = torch.cat(random_actions, dim=1)
                    actions = (1 - take_random) * to_numpy(onehot_actions) + take_random * to_numpy(random_actions)
                else:
                    onehot_actions = list(map(onehot_from_logits, actor_out))
                    actions = torch.cat(onehot_actions, dim=-1)

            else:
                if use_gumbel or (use_target and self.target_noise is not None):
                    actions = gumbel_softmax(actor_out, available_actions, hard=True, device=self.device)  # gumbel has a gradient
                elif explore:
                    onehot_actions = gumbel_softmax(actor_out, available_actions, hard=True, device=self.device)  # gumbel has a gradient
                    assert no_sequence, "Cannot do exploration on a sequence!"
                    # eps greedy exploration
                    eps = self.exploration.eval(t_env)
                    rand_numbers = np.random.rand(batch_size, 1)
                    # random actions sample uniformly from action space
                    logits = avail_choose(torch.ones(batch_size, self.act_dim), available_actions)
                    random_actions = OneHotCategorical(logits=logits).sample().numpy()
                    take_random = (rand_numbers < eps).astype(int)
                    actions = (1 - take_random) * to_numpy(onehot_actions) + take_random * random_actions
                else:
                    actions = onehot_from_logits(actor_out, available_actions)  # no gradient

        else:
            if explore:
                assert no_sequence, "Cannot do exploration on a sequence!"
                actions = gaussian_noise(actor_out.shape, self.args.act_noise_std) + actor_out
            elif use_target and self.target_noise is not None:
                assert isinstance(self.target_noise, float)
                actions = gaussian_noise(actor_out.shape, self.target_noise) + actor_out
            else:
                actions = actor_out
            # # clip the actions at the bounds of the action space
            # actions = torch.max(torch.min(actions, torch.from_numpy(self.act_space.high)), torch.from_numpy(self.act_space.low))

        return actions, new_rnn_states, eps

    def init_hidden(self, num_agents, batch_size):
        """See parent class."""
        if num_agents == -1:
            return torch.zeros(batch_size, self.hidden_size)
        else:
            return torch.zeros(num_agents, batch_size, self.hidden_size)

    def get_random_actions(self, obs, available_actions=None):
        """See parent class."""
        batch_size = obs.shape[0]

        if self.discrete:
            if self.multidiscrete:
                random_actions = [OneHotCategorical(logits=torch.ones(batch_size, self.act_dim[i])).sample().numpy() for i in
                                    range(len(self.act_dim))]
                random_actions = np.concatenate(random_actions, axis=-1)
            else:
                if available_actions is not None:
                    logits = avail_choose(torch.ones(batch_size, self.act_dim), available_actions)
                    random_actions = OneHotCategorical(logits=logits).sample().numpy()
                else:
                    random_actions = OneHotCategorical(logits=torch.ones(batch_size, self.act_dim)).sample().numpy()
        else:
            random_actions = np.random.uniform(self.act_space.low, self.act_space.high, size=(batch_size, self.act_dim))

        return random_actions

    def soft_target_updates(self):
        """Soft update the target networks through a Polyak averaging update."""
        soft_update(self.target_critic, self.critic, self.tau)
        soft_update(self.target_actor, self.actor, self.tau)

    def hard_target_updates(self):
        """Hard update target networks by copying the weights of the live networks."""
        hard_update(self.target_critic, self.critic)
        hard_update(self.target_actor, self.actor)


#!/usr/bin/env python
# coding: utf-8

# # Tidy Data and Pandas
#
# [**Pandas**](https://pandas.pydata.org/) is the most popular and widely used Python library for data wrangling and analysis. Developed just over 10 years ago in the financial services industry, pandas is now included in all major distributions of Python and has become a mainstay for doing data analysis in Python.
#
# **Tidy Data** is a small set a of core principles to streamline analysis and coding by organizing data into tables with a simple and standardized structure. Tidy Data is highly intuitive and well suited to Pandas.   Keeping data organized following "Tidy Data" principles means less time wrangling data, short and clear Python code for analysis, and more time to capture good data and gain insight..
#
# The purpose of this notebook is to get you started using Pandas with Tidy Data. Pandas is a full featured library capable of handling complex applications. In the spirit of the 80/20 rule (i.e., [Pareto principle]()), the goal here is to introduce just enough of the pandas library to handle routine data analysis tasks.
#
# Some useful references:
#
# * [Pandas Cheat Sheet](https://pandas.pydata.org/Pandas_Cheat_Sheet.pdf)
# * [Tidy Data](https://vita.had.co.nz/papers/tidy-data.pdf) paper by Hadley Wickham.
# * [Tidy Data](https://r4ds.had.co.nz/tidy-data.html?q=Tidy%20Data#non-tidy-data), Chapter 12 from R for Data Science.
# * [13 Most Important Pandas Functions for Data Science](https://www.analyticsvidhya.com/blog/2021/05/pandas-functions-13-most-important/)

# ## Tidy Data
#
# >The KonMari Method™ is not a quick fix for a messy room or a once-in-a-while approach to tidying.
# It’s a chance to reset your entire life – but only if you commit to following its principles. -- Marie Kondo
#
# Data acquired in process applications generally consists of repeated observation of a set of process variables. The values are usually numbers, such as temperature or pressure, but can also be strings, integers, or categorical data indicating the status of equipment or alarms.
#
# We assume data from repeated observations is arranged in tabular form in data files. Each distinct experiment, treatment, or unit of data is located in a separate file.
#
# * Every column of a data file holds all data for a unique variable.
# * Every row of a data file is an observation.
# * Every cell contains a single value.
#
# ![](https://r4ds.had.co.nz/images/tidy-1.png)
# Figure Source: Figure 12.1 from R for Data Science (Hadley Wickham & Garrett Grolemund)
#
# These assumptions are the ["Tidy Data"](https://vita.had.co.nz/papers/tidy-data.pdf) principles popularized by Hadley Wickham, and closely related to concepts underlying relational databases.
# Tiny Data assigns meaning to the structure of the data ffile, which significantly streamlines subsequenct coding and analysis. [Hart, et al., provide excellent contrasting examples of tidy and messy data](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1005097).
#
# These notes assume data is stored in data files organized using "Tiny Data" principles. If your data isn't organized like this, the procedures described by [Wickham](https://vita.had.co.nz/papers/tidy-data.pdf) may help reorgnize your data for more efficient analysis.

# ## Example: Messy Data
#
# The student group designed a series of experiments measuring the performance of the heat exchanger to high (H), medium (M), and low (L) flowrates for both the hot and cold streams --- a total of nine flowrate combinations. For each combination they reported data for three repeated observations. [A portion of their data is available on Google Sheets](https://docs.google.com/spreadsheets/d/1wuJq3B4z0tmTIsRpm5zZUP-PBBxT6OpuJKeBlQcQ4Z0/edit?usp=sharing). From this data they intend to compute the overall heat transfer coefficient $UA$, and attempt to fit a regression model for the heat transfer coefficients as a function of the flowrates.
#
# A screenshot of the data collected by the students is given below.
#
# ![](../../media/pandas-google-sheets-4.png)
#
# :::{admonition} Study Question
#
# Before reading further, can you find three ways this data set is not consistent with Tidy Data?
#
# :::
#
# :::{toggle}
#
# * Empty rows are included for formatting that do not contain observations.
# * Missing observations for trial 9.
# * The use of vertical merging to indicate values by position rather than by actual content.
# * The "Flow Rate (H/C)" includes two values in each cell.
# * Information in the first column is meta-data, not a useful value to include in the observations.
#
# :::

# ## Reading Tidy Data
#
# We assume data is organized by Tidy Data principles for the rest of this notebook, with each data set corresponding to an experiment or other clearly defined collection of observations.

# ### From .csv files
#
# For files stored in `.csv` format, a pandas **DataFrame** object is created with the [`read_csv(data_file)`](https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html). `data_file` is a string containing a url or the name of a local file. `read_csv()` function has many optional arguments, but for simple cases the path to the data file is often enough to do the job.

# In[2]:

import pandas as pd

data_file = "https://raw.githubusercontent.com/jckantor/cbe30338-book/main/notebooks/data/tclab-data-example.csv"
df = pd.read_csv(data_file)
display(df)

# ### From Google Sheets
#
# Google sheets are a convenient to collect and share data. There is a complete API and libraries to enable full, authenticated, read/write access to Google Sheets.
#
# But if the data is not confidential and can be temporarilty published to the web for public access, then it takes just a few steps and one line of Python to read the data as pandas DataFrame.
#
# The first step is publish the sheet to the web. In Google Sheets, select "File > Share > Publish to the Web".
#
# ![](../../media/pandas-google-sheets-1.png)
#
# In the dialog box, choose the "Link" tab. Locate the drop-down menus, then select the sheet to be published and "Comma-seperated values (.csv)" format. Click "Publish".
#
# ![](../../media/pandas-google-sheets-2.png)
#
# After confirming the choice to publish, a link url will appear.  This url can be treat as a link to a .csv file. Use `.read_csv()` to create a pandas dataframe.
#
# ![](../../media/pandas-google-sheets-3.png)
#
# Copy the url into the following cell to complete the operation.

# In[3]:

sheet_url = "https://docs.google.com/spreadsheets/d/e/2PACX-1vSNUCEFMaGZ-y18p-AnDoImEeenMLbRxXBABwFNeP8I3xiUejolPJx-kr4aUywD0szRel81Kftr8J0R/pub?gid=865146464&single=true&output=csv"
hx = pd.read_csv(sheet_url)
hx

# ### From Python
#
# Pandas dataframes can be created directly in Python. Here we demonstrate use of a Python dictionary to create a dataframe with data for variables computed as numpy arrays.

# In[4]:

import numpy as np

t = np.linspace(0, 10, 201)
s = np.sin(t)
c = np.cos(t)

df_np = pd.DataFrame({"time": t, "sin": s, "cos": c})
df_np

# ## Accessing Data
#
# The core object in pandas is the **DataFrame**. For "Tidy Data", a DataFrame will be collection of columns, each column containing observations of a single, unique variable. Each row is one observation of all variables.

# The **index** assigns a unique label to each row/observation.

# In[5]:

df.index

# The names of the **columns** are given by `.columns`.

# In[6]:

df.columns

# Each column forms a ``Series`` comprised of all observations of a variable. There are several common ways to access the data series for a variable. These produce the same result.
#
# * `df["T1"]`
# * `df.T1`
# * `df.loc[:, "T1"]`
#
# Which you choose depends on situation and context.

# In[7]:

df["T1"]

# The `.loc[row, column]` is used to extract slices of the dataframe. A single value is accessed by row index and column label.

# In[8]:

df.loc[3, "T1"]

# To extract values for multiple variables from a single observation.

# In[9]:

df.loc[3, ["T1", "T2"]]

# To extract a range of observations of one or more variables.

# In[10]:

df.loc[3:5, ["T1", "T2"]]

# Observations can be selected by conditions.

# In[11]:

df[(df.Time >= 100) & (df.Time <= 110)]

# ## Visualizing Data
#
# Pandas provides convenient tools for displaying data in tabular and graphical formats.

# ### Tabular Display
#
# The quickest way to display a dataframe as a table is with `display()`. Additional styling and formating options are available through a dataframe's `.style` property.

# In[12]:

display(df)

# For development it is often enough to view just the first few rows or last few rows of a dataframe. The dataframe methods `.head()` and `.tail()` provide this service.

# In[13]:

df.head(5)

# In[14]:

df.tail(5)

# ### Plotting
#
# An extensive variety of plots can be constructed using a dataframe's `.plot()` method. Many of the usual Matploblib plotting commands can be accessed through options passed to `.plot()`. For many routine applications, a single call of a dataframe's `.plot()` method can replace many lines of Python code using Matplotlib.
#
# For example, to plot all observations for a single variable.

# In[14]:

df.T1.plot()

# The `.plot()` method will often be used to plot one or more variables on the vertical 'y' axis as a function of another variable on the horizontal 'x' axes. Additional options specify line styles, grid, labels, titles, and much more.

# In[15]:

df.plot("Time", ["T1", "T2"], style={"T1":'rx', "T2":'g'}, lw=2, ms=3,
        ylabel="deg C", title="TC Lab", grid=True)

# In[16]:

df.plot(x = "Time", y=["T1", "T2"], subplots=True, figsize=(10, 2), grid=True, layout=(1, 2))

# In[17]:

df[(df.Time > 570) & (df.Time < 680)].plot(x="Time", y="T1", figsize=(12, 5), style={"T1":"s:"}, grid=True)

# ### Scatter Plots

# In[18]:

df.plot.scatter(x = "T1", y = "T2")

# ### Statistical Plots

# In[19]:

df[["T1", "T2"]].hist(bins=30, figsize=(10, 3))

# ## Concluding Remarks
#
# Learning Pandas and data analysis is like learning to ride a bicycle. Once you have the enough knowledge to get going, the next steps are about practice and learning by doing.
#
# This notobook provides basic knowledge to the use of pandas for data analysis for engineering applications. With Tidy Data principles, data analysis can be streamlined and intuitive. The next steps are to put these tools work on your data analaysis tasks, and explore other elements of the pandas library that can be useful in your work.


#!/usr/bin/env python
# coding: utf-8

# ## Tacotron 2 inference code
# Edit the variables **checkpoint_path** and **text** to match yours and run the entire code to generate plots of mel outputs, alignments and audio synthesis from the generated mel-spectrogram using Griffin-Lim.

# #### Import libraries and setup matplotlib

# In[1]:

import matplotlib
import matplotlib.pylab as plt

import IPython.display as ipd

import sys
sys.path.append('waveglow/')
import numpy as np
import torch

from hparams import create_hparams
from model import Tacotron2
from layers import TacotronSTFT, STFT
from audio_processing import griffin_lim
from train import load_model
from text import text_to_sequence
#from denoiser import Denoiser
import scipy
#import scipy.io.wavfile.write as write

from resemblyzer import VoiceEncoder, preprocess_wav
from pathlib import Path

from viphoneme import vi2IPA_split
delimit ="/"

hparams = create_hparams()
hparams.sampling_rate = 22050

## config here
checkpoint_path = "../drive/MyDrive/MultiSpeaker_Tacotron2/checkpoints/checkpoint_0"
model = load_model(hparams)
model.load_state_dict(torch.load(checkpoint_path)['state_dict'])
_ = model.cuda().eval().half()

## config here
waveglow_path = '../drive/MyDrive/MultiSpeaker_Tacotron2/checkpoints/waveglow_256channels_universal_v5.pt'
waveglow = torch.load(waveglow_path)['model']
waveglow.cuda().eval().half()
for k in waveglow.convinv:
    k.float()
#denoiser = Denoiser(waveglow)

## config here
text = "đây là câu thoại đơn giản"
text = vi2IPA_split(text, delimit)
sequence = np.array(text_to_sequence(text, ['english_cleaners']))[None, :]
sequence = torch.autograd.Variable(
    torch.from_numpy(sequence)).cuda().long()

###
## config here
fpath = Path("../1320_00000.mp3")
wav = preprocess_wav(fpath)

encoder = VoiceEncoder()
embed = encoder.embed_utterance(wav)
embed = torch.HalfTensor(embed).to("cuda")
###

mel_outputs, mel_outputs_postnet, _, alignments = model.inference(sequence, embed)
#plot_data((mel_outputs.float().data.cpu().numpy()[0],
#           mel_outputs_postnet.float().data.cpu().numpy()[0],
#           alignments.float().data.cpu().numpy()[0].T))

with torch.no_grad():
    audio = waveglow.infer(mel_outputs_postnet, sigma=0.666)

scipy.io.wavfile.write("../test.wav", hparams.sampling_rate, audio[0].cpu().numpy())
#audio_denoised = denoiser(audio, strength=0.01)[:, 0]
#ipd.Audio(audio_denoised.cpu().numpy(), rate=hparams.sampling_rate)


from .readpfm import readPFM
import sys
from matplotlib import pyplot as plt
import glob
import cv2
import numpy as np
import os
from texttable import Texttable
import subprocess
import pdb
import time
import PIL.Image
from subprocess import call
import errno

def mkdir_p(path):
    try:
        os.makedirs(path)
    except OSError as exc:  # Python >2.5
        if exc.errno == errno.EEXIST and os.path.isdir(path):
            pass
        else:
            raise

def run_command(command):
    p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    return iter(p.stdout.readline, b"")

def eval_mb(eval_dir, subset, rdir, res, method_name, img_name, th, has_mask=False):
    # res relative to F
    with open("%s/%sF/%s/calib.txt" % (eval_dir, subset, img_name)) as f:
        lines = f.readlines()
        rd = int(lines[7].split("=")[-1])
        max_disp = int(int(lines[6].split("=")[-1]) * res)
    command = "code/evaldisp %s/OO_OO%s/XX_XX/disp0%s.pfm %s/OO_OOF/XX_XX/disp0GT.pfm %f %d %d" % (
        eval_dir,
        rdir,
        method_name,
        eval_dir,
        th,
        max_disp,
        rd,
    )
    if has_mask:
        command += " OO_OOF/XX_XX/mask0nocc.png"

    command = command.replace("XX_XX", img_name)
    command = command.replace("OO_OO", subset)
    lines = [i for i in run_command(command.split())]
    return lines

def eval_carla(result_dir, method_name, img_name, th):
    image_suffix = img_name.split("/", 4)[-1].replace("/", "_").split(".")[0]
    command = "code/evaldisp %s/%s%s.pfm /data/gengshay/carla-new-eval/%s.pfm %f 768 0" % (
        result_dir,
        method_name,
        image_suffix,
        image_suffix.replace("cam0", "depth0"),
        th,
    )
    command = command.replace("XX_XX", img_name)
    lines = [i for i in run_command(command.split())]
    return lines

def run_iResNet(rdir, res):
    os.environ["CUDA_VISIBLE_DEVICES"] = "2"
    os.chdir("/home/gengshay/code/iResNet/models")
    command = "python test_rob.py iResNet_ROB.caffemodel %f" % (res * 2 / 1.5)
    lines = [i for i in run_command(command.split())]

    for filename in glob.glob("submission_results/Middlebury2014_iResNet_ROB/val/*"):
        imname = filename.split("/")[-1].strip()
        if os.path.isdir(filename):
            call(
                [
                    "cp",
                    "-rf",
                    "submission_results/Middlebury2014_iResNet_ROB/val/%s" % imname,
                    "/home/gengshay/code/MiddEval3/val%s/" % (rdir),
                ]
            )

def run_elas_carla(result_dir, res, img_name):
    max_disp = 768 * res
    im = cv2.imread(img_name)
    im = cv2.resize(im, None, fx=res, fy=res, interpolation=cv2.INTER_AREA)
    cv2.imwrite("./tmp0.png", im)
    im = cv2.imread(img_name.replace("cam0", "cam1"))
    im = cv2.resize(im, None, fx=res, fy=res, interpolation=cv2.INTER_AREA)
    cv2.imwrite("./tmp1.png", im)

    image_suffix = img_name.split("/", 4)[-1].replace("/", "_").split(".")[0]
    command = "alg-ELAS/run ./tmp0.png ./tmp1.png %d %s" % (max_disp, result_dir)
    lines = [i for i in run_command(command.split())]
    print(lines)

    command = "mv %s/disp0.pfm %s/%s.pfm " % (result_dir, result_dir, image_suffix)
    lines = [i for i in run_command(command.split())]
    print(lines)

def run_elas(eval_dir, subset, rdir, res, img_name):
    # res: resolution to upscale, not resolution compared to Full size
    mkdir_p("%s/%s%s/%s" % (eval_dir, subset, rdir, img_name))
    with open("%s/%sF/%s/calib.txt" % (eval_dir, subset, img_name)) as f:
        lines = f.readlines()
        max_disp = int(int(lines[6].split("=")[-1]) * res)
    if rdir == "H":
        max_disp = max_disp // 2
    elif rdir == "Q":
        max_disp = max_disp // 4
    if rdir == "A":
        # max_disp = max_disp * res
        indir = "F"
    else:
        indir = rdir
    im = cv2.imread("%s/%s%s/%s/im0.png" % (eval_dir, subset, indir, img_name))
    im = cv2.resize(im, None, fx=res, fy=res, interpolation=cv2.INTER_AREA)
    cv2.imwrite("./tmp0.png", im)
    im = cv2.imread("%s/%s%s/%s/im1.png" % (eval_dir, subset, indir, img_name))
    im = cv2.resize(im, None, fx=res, fy=res, interpolation=cv2.INTER_AREA)
    cv2.imwrite("./tmp1.png", im)

    command = "alg-ELAS/run ./tmp0.png ./tmp1.png %d %s/%s%s/%s/" % (max_disp, eval_dir, subset, rdir, img_name)
    lines = [i for i in run_command(command.split())]
    print(lines)

    command = "mv %s/OO_OO%s/XX_XX/time.txt %s/OO_OO%s/XX_XX/timeELAS.txt" % (eval_dir, rdir, eval_dir, rdir)
    command = command.replace("XX_XX", img_name)
    command = command.replace("OO_OO", subset)
    lines = [i for i in run_command(command.split())]
    print(lines)

    command = "mv %s/OO_OO%s/XX_XX/disp0_s.pfm %s/OO_OO%s/XX_XX/disp0ELAS_s.pfm" % (eval_dir, rdir, eval_dir, rdir)
    command = command.replace("XX_XX", img_name)
    command = command.replace("OO_OO", subset)
    lines = [i for i in run_command(command.split())]
    print(lines)

    command = "mv %s/OO_OO%s/XX_XX/disp0.pfm %s/OO_OO%s/XX_XX/disp0ELAS.pfm" % (eval_dir, rdir, eval_dir, rdir)
    command = command.replace("XX_XX", img_name)
    command = command.replace("OO_OO", subset)
    lines = [i for i in run_command(command.split())]
    print(lines)

def run_sgm(eval_dir, subset, rdir, res, img_name):
    with open("%s/%sF/%s/calib.txt" % (eval_dir, subset, img_name)) as f:
        lines = f.readlines()
        max_disp = int(int(lines[6].split("=")[-1]) * res)
    if rdir == "H":
        max_disp = max_disp // 2
    elif rdir == "Q":
        max_disp = max_disp // 4
    if rdir == "A":
        indir = "F"
    else:
        indir = rdir
    im = cv2.imread("%s/%s%s/%s/im0.png" % (eval_dir, subset, indir, img_name))
    im = cv2.resize(im, None, fx=res, fy=res, interpolation=cv2.INTER_AREA)
    im = np.pad(im, ((max_disp * 2, max_disp * 2), (max_disp * 2, max_disp * 2), (0, 0)), mode="constant")
    cv2.imwrite("%s/%s%s/%s/im0tmp.png" % (eval_dir, subset, rdir, img_name), im)
    im = cv2.imread("%s/%s%s/%s/im1.png" % (eval_dir, subset, indir, img_name))
    im = cv2.resize(im, None, fx=res, fy=res, interpolation=cv2.INTER_AREA)
    im = np.pad(im, ((max_disp * 2, max_disp * 2), (max_disp * 2, max_disp * 2), (0, 0)), mode="constant")
    cv2.imwrite("%s/%s%s/%s/im1tmp.png" % (eval_dir, subset, rdir, img_name), im)

    # prepare images
    command = (
        "SGM/app %s/OO_OO%s/XX_XX/im0tmp.png %s/OO_OO%s/XX_XX/im1tmp.png -dst_path=%s/OO_OO%s/XX_XX/disp0tmp.png -max_disparity=%d -no-downscale"
        % (eval_dir, rdir, eval_dir, rdir, eval_dir, rdir, max_disp)
    )
    # command = 'SGM/spsstereo OO_OO%s/XX_XX/im0tmp.png OO_OO%s/XX_XX/im1tmp.png' % (rdir,rdir)
    command = command.replace("XX_XX", img_name)
    command = command.replace("OO_OO", subset)
    begt = time.time()
    lines = [i for i in run_command(command.split())]
    totalt = time.time() - begt
    with open("%s/%s%s/%s/timeSGM.txt" % (eval_dir, subset, rdir, img_name), "w") as f:
        f.write(str(totalt))
    print(lines)

    # save pfm
    disp = np.asarray(PIL.Image.open("%s/%s%s/%s/disp0tmp.png" % (eval_dir, subset, rdir, img_name)))[
        max_disp * 2 : -max_disp * 2, max_disp * 2 : -max_disp * 2
    ]
    # disp = np.asarray(PIL.Image.open('im0tmp_left_disparity.png'))
    with open("%s/%s%s/%s/disp0SGM.pfm" % (eval_dir, subset, rdir, img_name), "w") as f:
        save_pfm(f, disp.astype(np.float32)[::-1], scale=1.0 / max_disp)
    # t1,t2=readPFM('%s%s/%s/disp0SGM.pfm'%(subset,res,img_name))
    command = "rm %s/OO_OO%s/XX_XX/disp0tmp.png" % (eval_dir, rdir)
    # command = 'rm im0tmp_left_disparity.png'
    command = command.replace("XX_XX", img_name)
    command = command.replace("OO_OO", subset)
    lines = [i for i in run_command(command.split())]
    print(lines)

    command = "rm %s/OO_OO%s/XX_XX/im0tmp.png" % (eval_dir, rdir)
    command = command.replace("XX_XX", img_name)
    command = command.replace("OO_OO", subset)
    lines = [i for i in run_command(command.split())]
    print(lines)

    command = "rm %s/OO_OO%s/XX_XX/im1tmp.png" % (eval_dir, rdir)
    command = command.replace("XX_XX", img_name)
    command = command.replace("OO_OO", subset)
    lines = [i for i in run_command(command.split())]
    print(lines)

def run_mccnn(eval_dir, subset, rdir, res, img_name):
    with open("%s/%sF/%s/calib.txt" % (eval_dir, subset, img_name)) as f:
        lines = f.readlines()
        max_disp = int(int(lines[6].split("=")[-1]) * res)
    if rdir == "H":
        max_disp = max_disp // 2
    elif rdir == "Q":
        max_disp = max_disp // 4
    if rdir == "A":
        indir = "F"
    else:
        indir = rdir
    im = cv2.imread("%s/%s%s/%s/im0.png" % (eval_dir, subset, indir, img_name))
    im = cv2.resize(im, None, fx=res, fy=res, interpolation=cv2.INTER_AREA)
    # im = np.pad(im, ((max_disp*2,max_disp*2),(max_disp*2,max_disp*2),(0,0)),mode='constant')
    cv2.imwrite("%s/%s%s/%s/im0tmp.png" % (eval_dir, subset, rdir, img_name), im)
    im = cv2.imread("%s/%s%s/%s/im1.png" % (eval_dir, subset, indir, img_name))
    im = cv2.resize(im, None, fx=res, fy=res, interpolation=cv2.INTER_AREA)
    # im = np.pad(im, ((max_disp*2,max_disp*2),(max_disp*2,max_disp*2),(0,0)),mode='constant')
    cv2.imwrite("%s/%s%s/%s/im1tmp.png" % (eval_dir, subset, rdir, img_name), im)

    # prepare images
    os.chdir("mc-cnn")
    command = (
        "./main.lua mb slow -a predict -net_fname net/net_mb_slow_-a_train_all.t7 -left %s/OO_OO%s/XX_XX/im0tmp.png -right %s/OO_OO%s/XX_XX/im1tmp.png -disp_max %d"
        % (eval_dir, rdir, eval_dir, rdir, max_disp)
    )
    command = command.replace("XX_XX", img_name)
    command = command.replace("OO_OO", subset)
    begt = time.time()
    lines = [i for i in run_command(command.split())]
    totalt = time.time() - begt
    with open("%s/%s%s/%s/timeMCCNN.txt" % (eval_dir, subset, rdir, img_name), "w") as f:
        f.write(str(totalt))
    print(lines)

    command = "luajit samples/bin2png.lua %d %d %d" % (max_disp, im.shape[0], im.shape[1])
    lines = [i for i in run_command(command.split())]
    print(lines)

    # save pfm
    # disp = np.asarray(PIL.Image.open('disp.png'))[max_disp*2:-max_disp*2,max_disp*2:-max_disp*2]
    disp = np.asarray(PIL.Image.open("disp.png")).astype(float) / 2
    #    disp = np.asarray(PIL.Image.open('left.png')).astype(float)
    #    disp = disp/disp.max() * max_disp
    with open("%s/%s%s/%s/disp0MCCNN.pfm" % (eval_dir, subset, rdir, img_name), "w") as f:
        save_pfm(f, disp.astype(np.float32)[::-1], scale=1.0 / max_disp)
    os.chdir("../")
    command = "rm %s/OO_OO%s/XX_XX/im0tmp.png" % (eval_dir, rdir)
    command = command.replace("XX_XX", img_name)
    command = command.replace("OO_OO", subset)
    lines = [i for i in run_command(command.split())]
    print(lines)

    command = "rm %s/OO_OO%s/XX_XX/im1tmp.png" % (eval_dir, rdir)
    command = command.replace("XX_XX", img_name)
    command = command.replace("OO_OO", subset)
    lines = [i for i in run_command(command.split())]
    print(lines)

"""
Save a Numpy array to a PFM file.
"""

def save_pfm(file, image, scale=1):
    color = None

    if image.dtype.name != "float32":
        raise Exception("Image dtype must be float32.")

    if len(image.shape) == 3 and image.shape[2] == 3:  # color image
        color = True
    elif len(image.shape) == 2 or len(image.shape) == 3 and image.shape[2] == 1:  # greyscale
        color = False
    else:
        raise Exception("Image must have H x W x 3, H x W x 1 or H x W dimensions.")

    file.write("PF\n" if color else "Pf\n")
    file.write("%d %d\n" % (image.shape[1], image.shape[0]))

    endian = image.dtype.byteorder

    if endian == "<" or endian == "=" and sys.byteorder == "little":
        scale = -scale

    file.write("%f\n" % scale)

    image.tofile(file)


from __future__ import division, absolute_import, print_function
from past.builtins import xrange

import numpy as np
import scipy.interpolate as interpolate
import scipy.integrate as integrate
import os
import sys
from pkg_resources import resource_filename

from .modtranGenerator import ModtranGenerator
from .fgcmAtmosphereTable import FgcmAtmosphereTable

from .sharedNumpyMemManager import SharedNumpyMemManager as snmm
from .fgcmLogger import FgcmLogger

class FgcmLUTMaker(object):
    """
    Class to make a look-up table.

    parameters
    ----------
    lutConfig: dict
       Dictionary with LUT config variables
    makeSeds: bool, default=False
       Make a SED-table in the look-up table (experimental)
    """

    def __init__(self,lutConfig,makeSeds=False):
        self._checkLUTConfig(lutConfig)

        self.magConstant = 2.5/np.log(10)
        self._setThroughput = False
        self.makeSeds = makeSeds

        try:
            self.stellarTemplateFile = resource_filename(__name__,'data/templates/stellar_templates_master.fits')
        except:
            raise IOError("Could not find stellar template file")

        if (not os.path.isfile(self.stellarTemplateFile)):
            raise IOError("Could not find stellar template file")

        if 'logger' in lutConfig:
            self.fgcmLog = lutConfig['logger']
        else:
            self.fgcmLog = FgcmLogger('dummy.log', 'INFO', printLogger=True)

    def _checkLUTConfig(self,lutConfig):
        """
        Internal method to check the lutConfig dictionary

        parameters
        ----------
        lutConfig: dict
        """

        self.runModtran = False

        requiredKeys = ['filterNames', 'stdFilterNames', 'nCCD']

        # first: check if there is a tableName here!
        if 'atmosphereTableName' in lutConfig:
            # Can we find this table?

            # load parameters from it and stuff into config dict
            self.atmosphereTable = FgcmAtmosphereTable.initWithTableName(lutConfig['atmosphereTableName'])

            # look for consistency between configs?
            # Note that we're assuming if somebody asked for a table they wanted
            # what was in the table
            for key in self.atmosphereTable.atmConfig:
                if key in lutConfig:
                    if 'Range' in key:
                        if (not np.isclose(lutConfig[key][0], self.atmosphereTable.atmConfig[key][0]) or
                            not np.isclose(lutConfig[key][1], self.atmosphereTable.atmConfig[key][1])):
                            print("Warning: input config %s is %.5f-%.5f but precomputed table is %.5f-%.5f" %
                                  (key, lutConfig[key][0], lutConfig[key][1],
                                   self.atmosphereTable.atmConfig[key][0],
                                   self.atmosphereTable.atmConfig[key][1]))
                    else:
                        if not np.isclose(lutConfig[key], self.atmosphereTable.atmConfig[key]):
                            print("Warning: input config %s is %.5f but precomputed table is %.5f" %
                                  (key, lutConfig[key], self.atmosphereTable.atmConfig[key]))

        else:
            # regular config with parameters
            self.runModtran = True
            self.atmosphereTable = FgcmAtmosphereTable(lutConfig)

        for key in requiredKeys:
            if (key not in lutConfig):
                raise ValueError("required %s not in lutConfig" % (key))
            if ('Range' in key):
                if (len(lutConfig[key]) != 2):
                    raise ValueError("%s must have 2 elements" % (key))

        self.lutConfig = lutConfig

        self.filterNames = self.lutConfig['filterNames']
        self.stdFilterNames = self.lutConfig['stdFilterNames']

        if len(self.filterNames) != len(self.stdFilterNames):
            raise ValueError("Length of filterNames must be same as stdFilterNames")

        for stdFilterName in self.stdFilterNames:
            if stdFilterName not in self.filterNames:
                raise ValueError("stdFilterName %s not in list of filterNames" % (stdFilterName))

        self.nCCD = self.lutConfig['nCCD']
        self.nCCDStep = self.nCCD+1

        # and record the standard values out of the config
        #  (these will also come out of the save file)

        self.pmbStd = self.atmosphereTable.atmConfig['pmbStd']
        self.pwvStd = self.atmosphereTable.atmConfig['pwvStd']
        self.o3Std = self.atmosphereTable.atmConfig['o3Std']
        self.tauStd = self.atmosphereTable.atmConfig['tauStd']
        self.lnTauStd = np.log(self.tauStd)
        self.alphaStd = self.atmosphereTable.atmConfig['alphaStd']
        self.secZenithStd = self.atmosphereTable.atmConfig['airmassStd']
        self.zenithStd = np.arccos(1./self.secZenithStd)*180./np.pi

        if ('lambdaRange' in self.atmosphereTable.atmConfig):
            self.lambdaRange = np.array(self.atmosphereTable.atmConfig['lambdaRange'])
        else:
            self.lambdaRange = np.array([3000.0,11000.0])

        if ('lambdaStep' in self.atmosphereTable.atmConfig):
            self.lambdaStep = self.atmosphereTable.atmConfig['lambdaStep']
        else:
            self.lambdaStep = 0.5

        if ('lambdaNorm' in self.atmosphereTable.atmConfig):
            self.lambdaNorm = self.atmosphereTable.atmConfig['lambdaNorm']
        else:
            self.lambdaNorm = 7750.0

    def setThroughputs(self, throughputDict):
        """
        Set the throughputs per CCD

        parameters
        ----------
        throughputDict: dict
           Dict with throughput information

        The throughput dict should have one entry for each filterName.
        Each of these elements should be a dictionary with the following keys:
           'LAMBDA': numpy float array with wavelength values
           ccd_index: numpy float array with throughput values for the ccd_index
        There should be one entry for each CCD.
        """

        self.inThroughputs=[]
        for filterName in self.filterNames:
            try:
                lam = throughputDict[filterName]['LAMBDA']
            except:
                raise ValueError("Wavelength LAMBDA not found for filter %s in throughputDict!" % (filterName))

            tput = np.zeros(lam.size, dtype=[('LAMBDA','f4'),
                                             ('THROUGHPUT_AVG','f4'),
                                             ('THROUGHPUT_CCD','f4',self.nCCD)])
            tput['LAMBDA'][:] = lam
            for ccdIndex in xrange(self.nCCD):
                try:
                    tput['THROUGHPUT_CCD'][:,ccdIndex] = throughputDict[filterName][ccdIndex]
                except:
                    raise ValueError("CCD Index %d not found for filter %s in throughputDict!" % (ccdIndex,filterName))

            # check if the average is there, if not compute it
            if ('AVG' in throughputDict[filterName]):
                tput['THROUGHPUT_AVG'][:] = throughputDict[filterName]['AVG']
            else:
                self.fgcmLog.info("Average throughput not found in throughputDict for filter %s.  Computing now..." % (filterName))
                for i in xrange(lam.size):
                    use,=np.where(tput['THROUGHPUT_CCD'][i,:] > 0.0)
                    if (use.size > 0):
                        tput['THROUGHPUT_AVG'][i] = np.mean(tput['THROUGHPUT_CCD'][i,use])

            self.inThroughputs.append(tput)

        self._setThroughput = True

    def makeLUT(self):
        """
        Make the look-up table.  This can either be saved with saveLUT or accessed via
         attributes.

        parameters
        ----------
        None

        output attributes
        -----------------
        pmb: float array
           Pressure (millibars)
        pmbFactor: float array
           Pressure factor
        pmbElevation: float
           Standard PMB at elevation
        pwv: float array
           Water vapor array
        o3: float array
           Ozone array
        tau: float array
           Aerosol optical index array
        lambdaNorm: float
           Aerosol normalization wavelength
        alpha: float array
           Aerosol slope array
        zenith: float array
           Zenith angle array
        nccd: int
           Number of CCDs in table
        pmbStd: float
           Standard PMB
        pwvStd: float
           Standard PWV
        o3Std: float
           Standard O3
        tauStd: float
           Standard tau
        alphaStd: float
           Standard alpha
        zenithStd: float
           Standard zenith angle (deg)
        lambdaRange: numpy float array
           Wavelength range (A)
        lambdaStep: float
           Wavelength step (A)
        lambdaStd: numpy float array
           Standard wavelength for each filterName (A) at each standard band
        lambdaStdRaw: numpy float array
           Standard wavelength for each filterName (A)
        I0Std: numpy float array
           Standard value for I0 for each filterName
        I1Std: numpy float array
           Standard value for I1 for each filterName
        I10Std: numpy float array
           Standard value for I10 for each filterName
        lambdaB: numpy float array
           Standard wavelength for each filterName (A) assuming no atmosphere
        atmLambda: numpy float array
           Standard atmosphere wavelength array (A)
        atmStdTrans: numpy float array
           Standard atmosphere transmission array
        lut: Look-up table recarray
           lut['I0']: I0 LUT (multi-dimensional)
           lut['I1']: I1 LUT (multi-dimensional)
        lutDeriv: Derivative recarray
           lutDeriv['D_PMB']: I0 PMB derivative
           lutDeriv['D_PWV']: I0 PWV derivative
           lutDeriv['D_O3']: I0 O3 derivative
           lutDeriv['D_LNTAU']: I0 log(tau) derivative
           lutDeriv['D_ALPHA']: I0 alpha derivative
           lutDeriv['D_SECZENITH']: I0 sec(zenith) derivative
           lutDeriv['D_PMB_I1']: I1 PMB derivative
           lutDeriv['D_PWV_I1']: I1 PWV derivative
           lutDeriv['D_O3_I1']: I1 O3 derivative
           lutDeriv['D_LNTAU_I1']: I1 log(tau) derivative
           lutDeriv['D_ALPHA_I1']: I1 alpha derivative
           lutDeriv['D_SECZENITH_I1']: I0 sec(zenith) derivative
        """

        if not self._setThroughput:
            raise ValueError("Must set the throughput before running makeLUT")

        if self.runModtran:
            # need to build the table
            self.atmosphereTable.generateTable()
        else:
            # load from data
            self.atmosphereTable.loadTable()

        # and grab from the table
        self.atmLambda = self.atmosphereTable.atmLambda
        self.atmStdTrans = self.atmosphereTable.atmStdTrans

        self.pmbElevation = self.atmosphereTable.pmbElevation

        self.pmb = self.atmosphereTable.pmb
        self.pmbDelta = self.atmosphereTable.pmbDelta
        pmbPlus = np.append(self.pmb, self.pmb[-1] + self.pmbDelta)

        self.pwv = self.atmosphereTable.pwv
        self.pwvDelta = self.atmosphereTable.pwvDelta
        pwvPlus = np.append(self.pwv, self.pwv[-1] + self.pwvDelta)

        self.o3 = self.atmosphereTable.o3
        self.o3Delta = self.atmosphereTable.o3Delta
        o3Plus = np.append(self.o3, self.o3[-1] + self.o3Delta)

        self.lnTau = self.atmosphereTable.lnTau
        self.lnTauDelta = self.atmosphereTable.lnTauDelta
        self.tau = np.exp(self.lnTau)
        lnTauPlus = np.append(self.lnTau, self.lnTau[-1] + self.lnTauDelta)
        tauPlus = np.exp(lnTauPlus)

        self.alpha = self.atmosphereTable.alpha
        self.alphaDelta = self.atmosphereTable.alphaDelta
        alphaPlus = np.append(self.alpha, self.alpha[-1] + self.alphaDelta)

        self.secZenith = self.atmosphereTable.secZenith
        self.secZenithDelta = self.atmosphereTable.secZenithDelta
        self.zenith = np.arccos(1./self.secZenith)*180./np.pi
        secZenithPlus = np.append(self.secZenith, self.secZenith[-1] + self.secZenithDelta)
        zenithPlus = np.arccos(1./secZenithPlus)*180./np.pi

        # and compute the proper airmass...
        self.airmass = self.secZenith - 0.0018167*(self.secZenith-1.0) - 0.002875*(self.secZenith-1.0)**2.0 - 0.0008083*(self.secZenith-1.0)**3.0
        airmassPlus = secZenithPlus - 0.0018167*(secZenithPlus-1.0) - 0.002875*(secZenithPlus-1.0)**2.0 - 0.0008083*(secZenithPlus-1.0)**3.0

        pwvAtmTable = self.atmosphereTable.pwvAtmTable
        o3AtmTable = self.atmosphereTable.o3AtmTable
        o2AtmTable = self.atmosphereTable.o2AtmTable
        rayleighAtmTable = self.atmosphereTable.rayleighAtmTable

        # get the filters over the same lambda ranges...
        self.fgcmLog.info("\nInterpolating filters...")
        self.throughputs = []
        for i in xrange(len(self.filterNames)):
            inLam = self.inThroughputs[i]['LAMBDA']

            tput = np.zeros(self.atmLambda.size, dtype=[('LAMBDA','f4'),
                                                        ('THROUGHPUT_AVG','f4'),
                                                        ('THROUGHPUT_CCD','f4',self.nCCD)])
            tput['LAMBDA'][:] = self.atmLambda

            for ccdIndex in xrange(self.nCCD):
                ifunc = interpolate.interp1d(inLam, self.inThroughputs[i]['THROUGHPUT_CCD'][:,ccdIndex])
                tput['THROUGHPUT_CCD'][:,ccdIndex] = np.clip(ifunc(self.atmLambda),
                                                             0.0,
                                                             1e100)
            ifunc = interpolate.interp1d(inLam, self.inThroughputs[i]['THROUGHPUT_AVG'])
            tput['THROUGHPUT_AVG'][:] = np.clip(ifunc(self.atmLambda), 0.0, 1e100)

            self.throughputs.append(tput)

        # and now we can get the standard atmosphere and lambda_b
        self.fgcmLog.info("Computing lambdaB")
        self.lambdaB = np.zeros(len(self.filterNames))
        for i in xrange(len(self.filterNames)):
            num = integrate.simps(self.atmLambda * self.throughputs[i]['THROUGHPUT_AVG'] / self.atmLambda, self.atmLambda)
            denom = integrate.simps(self.throughputs[i]['THROUGHPUT_AVG'] / self.atmLambda, self.atmLambda)
            self.lambdaB[i] = num / denom
            self.fgcmLog.info("Filter: %s, lambdaB = %.3f" % (self.filterNames[i], self.lambdaB[i]))

        self.fgcmLog.info("Computing lambdaStdFilter")
        self.lambdaStdFilter = np.zeros(len(self.filterNames))
        for i in xrange(len(self.filterNames)):
            num = integrate.simps(self.atmLambda * self.throughputs[i]['THROUGHPUT_AVG'] * self.atmStdTrans / self.atmLambda, self.atmLambda)
            denom = integrate.simps(self.throughputs[i]['THROUGHPUT_AVG'] * self.atmStdTrans / self.atmLambda, self.atmLambda)
            self.lambdaStdFilter[i] = num / denom
            self.fgcmLog.info("Filter: %s, lambdaStdFilter = %.3f" % (self.filterNames[i],self.lambdaStdFilter[i]))

        # now compute lambdaStd based on the desired standards...
        self.fgcmLog.info("Calculating lambdaStd")
        self.lambdaStd = np.zeros(len(self.filterNames))

        for i, filterName in enumerate(self.filterNames):
            ind = self.filterNames.index(self.stdFilterNames[i])
            #ind, = np.where(self.filterNames == self.stdFilterNames[i])
            #self.lambdaStd[i] = self.lambdaStdFilter[ind[0]]
            self.lambdaStd[i] = self.lambdaStdFilter[ind]
            self.fgcmLog.info("Filter: %s (from %s) lambdaStd = %.3f" %
                              (filterName, self.stdFilterNames[i], self.lambdaStd[i]))

        self.fgcmLog.info("Computing I0Std/I1Std")
        self.I0Std = np.zeros(len(self.filterNames))
        self.I1Std = np.zeros(len(self.filterNames))

        for i in xrange(len(self.filterNames)):
            self.I0Std[i] = integrate.simps(self.throughputs[i]['THROUGHPUT_AVG'] * self.atmStdTrans / self.atmLambda, self.atmLambda)
            self.I1Std[i] = integrate.simps(self.throughputs[i]['THROUGHPUT_AVG'] * self.atmStdTrans * (self.atmLambda - self.lambdaStd[i]) / self.atmLambda, self.atmLambda)

        self.I10Std = self.I1Std / self.I0Std

        #################################
        ## Make the I0/I1 LUT
        #################################

        self.fgcmLog.info("Building look-up table...")
        lutPlus = np.zeros((len(self.filterNames),
                            pwvPlus.size,
                            o3Plus.size,
                            tauPlus.size,
                            alphaPlus.size,
                            zenithPlus.size,
                            self.nCCDStep),
                           dtype=[('I0','f4'),
                                  ('I1','f4')])

        # pre-compute pmb factors
        pmbMolecularScattering = np.exp(-(pmbPlus - self.pmbElevation)/self.pmbElevation)
        pmbMolecularAbsorption = pmbMolecularScattering ** 0.6
        pmbFactorPlus = pmbMolecularScattering * pmbMolecularAbsorption
        self.pmbFactor = pmbFactorPlus[:-1]

        # this set of nexted for loops could probably be vectorized in some way
        for i in xrange(len(self.filterNames)):
            self.fgcmLog.info("Working on filter %s" % (self.filterNames[i]))
            for j in xrange(pwvPlus.size):
                self.fgcmLog.info("  and on pwv #%d" % (j))
                for k in xrange(o3Plus.size):
                    self.fgcmLog.info("   and on o3 #%d" % (k))
                    for m in xrange(tauPlus.size):
                        for n in xrange(alphaPlus.size):
                            for o in xrange(zenithPlus.size):
                                aerosolTauLambda = np.exp(-1.0*tauPlus[m]*airmassPlus[o]*(self.atmLambda/self.lambdaNorm)**(-alphaPlus[n]))
                                for p in xrange(self.nCCDStep):
                                    if (p == self.nCCD):
                                        Sb = self.throughputs[i]['THROUGHPUT_AVG'] * o2AtmTable[o,:] * rayleighAtmTable[o,:] * pwvAtmTable[j,o,:] * o3AtmTable[k,o,:] * aerosolTauLambda
                                    else:
                                        Sb = self.throughputs[i]['THROUGHPUT_CCD'][:,p] * o2AtmTable[o,:] * rayleighAtmTable[o,:] * pwvAtmTable[j,o,:] * o3AtmTable[k,o,:] * aerosolTauLambda

                                    lutPlus['I0'][i,j,k,m,n,o,p] = integrate.simps(Sb / self.atmLambda, self.atmLambda)
                                    lutPlus['I1'][i,j,k,m,n,o,p] = integrate.simps(Sb * (self.atmLambda - self.lambdaStd[i]) / self.atmLambda, self.atmLambda)

        # and create the LUT (not plus)
        self.lut = np.zeros((len(self.filterNames),
                             self.pwv.size,
                             self.o3.size,
                             self.tau.size,
                             self.alpha.size,
                             self.zenith.size,
                             self.nCCDStep),
                            dtype=lutPlus.dtype)

        temp = np.delete(lutPlus['I0'], self.pwv.size, axis=1)
        temp = np.delete(temp, self.o3.size, axis=2)
        temp = np.delete(temp, self.tau.size, axis=3)
        temp = np.delete(temp, self.alpha.size, axis=4)
        temp = np.delete(temp, self.zenith.size, axis=5)

        self.lut['I0'] = temp

        temp = np.delete(lutPlus['I1'], self.pwv.size, axis=1)
        temp = np.delete(temp, self.o3.size, axis=2)
        temp = np.delete(temp, self.tau.size, axis=3)
        temp = np.delete(temp, self.alpha.size, axis=4)
        temp = np.delete(temp, self.zenith.size, axis=5)

        self.lut['I1'] = temp

        #################################
        ## Make the I0/I1 derivative LUTs
        #################################

        # This is *not* done plus-size

        self.lutDeriv = np.zeros((len(self.filterNames),
                                  self.pwv.size,
                                  self.o3.size,
                                  self.tau.size,
                                  self.alpha.size,
                                  self.zenith.size,
                                  self.nCCDStep),
                                 dtype=[('D_PMB','f4'),
                                        ('D_PWV','f4'),
                                        ('D_O3','f4'),
                                        ('D_LNTAU','f4'),
                                        ('D_ALPHA','f4'),
                                        ('D_SECZENITH','f4'),
                                        ('D_PMB_I1','f4'),
                                        ('D_PWV_I1','f4'),
                                        ('D_O3_I1','f4'),
                                        ('D_LNTAU_I1','f4'),
                                        ('D_ALPHA_I1','f4'),
                                        ('D_SECZENITH_I1','f4')])

        self.fgcmLog.info("Computing derivatives...")

        ## FIXME: figure out PMB derivative?

        for i in xrange(len(self.filterNames)):
            self.fgcmLog.info("Working on filter %s" % (self.filterNames[i]))
            for j in xrange(self.pwv.size):
                for k in xrange(self.o3.size):
                    for m in xrange(self.tau.size):
                        for n in xrange(self.alpha.size):
                            for o in xrange(self.zenith.size):
                                for p in xrange(self.nCCDStep):
                                    self.lutDeriv['D_PWV'][i,j,k,m,n,o,p] = (
                                        ((lutPlus['I0'][i,j+1,k,m,n,o,p] -
                                          lutPlus['I0'][i,j,k,m,n,o,p]) /
                                         self.pwvDelta)
                                        )
                                    self.lutDeriv['D_PWV_I1'][i,j,k,m,n,o,p] = (
                                        ((lutPlus['I1'][i,j+1,k,m,n,o,p] -
                                          lutPlus['I1'][i,j,k,m,n,o,p]) /
                                         self.pwvDelta)
                                        )
                                    self.lutDeriv['D_O3'][i,j,k,m,n,o,p] = (
                                        ((lutPlus['I0'][i,j,k+1,m,n,o,p] -
                                          lutPlus['I0'][i,j,k,m,n,o,p]) /
                                         self.o3Delta)
                                        )
                                    self.lutDeriv['D_O3_I1'][i,j,k,m,n,o,p] = (
                                        ((lutPlus['I1'][i,j,k+1,m,n,o,p] -
                                          lutPlus['I1'][i,j,k,m,n,o,p]) /
                                         self.o3Delta)
                                        )
                                    self.lutDeriv['D_LNTAU'][i,j,k,m,n,o,p] = (
                                        ((lutPlus['I0'][i,j,k,m+1,n,o,p] -
                                          lutPlus['I0'][i,j,k,m,n,o,p]) /
                                         self.lnTauDelta)
                                        )
                                    self.lutDeriv['D_LNTAU_I1'][i,j,k,m,n,o,p] = (
                                        ((lutPlus['I1'][i,j,k,m+1,n,o,p] -
                                          lutPlus['I1'][i,j,k,m,n,o,p]) /
                                         self.lnTauDelta)
                                        )
                                    self.lutDeriv['D_ALPHA'][i,j,k,m,n,o,p] = (
                                        ((lutPlus['I0'][i,j,k,m,n+1,o,p] -
                                          lutPlus['I0'][i,j,k,m,n,o,p]) /
                                         self.alphaDelta)
                                        )
                                    self.lutDeriv['D_ALPHA_I1'][i,j,k,m,n,o,p] = (
                                        ((lutPlus['I1'][i,j,k,m,n+1,o,p] -
                                          lutPlus['I1'][i,j,k,m,n,o,p]) /
                                         self.alphaDelta)
                                        )
                                    self.lutDeriv['D_SECZENITH'][i,j,k,m,n,o,p] = (
                                        ((lutPlus['I0'][i,j,k,m,n,o+1,p] -
                                          lutPlus['I0'][i,j,k,m,n,o,p]) /
                                         self.secZenithDelta)
                                        )
                                    self.lutDeriv['D_SECZENITH_I1'][i,j,k,m,n,o,p] = (
                                        ((lutPlus['I1'][i,j,k,m,n,o+1,p] -
                                          lutPlus['I1'][i,j,k,m,n,o,p]) /
                                         self.secZenithDelta)
                                        )

        if (self.makeSeds):
            # and the SED LUT
            self.fgcmLog.info("Building SED LUT")

            # arbitrary.  Configure?  Fit?  Seems stable...
            delta = 600.0

            # blah on fits here...
            import fitsio

            # how many extensions?
            fits=fitsio.FITS(self.stellarTemplateFile)
            fits.update_hdu_list()
            extNames = []
            for hdu in fits.hdu_list:
                extName = hdu.get_extname()
                if ('TEMPLATE_' in extName):
                    extNames.append(extName)

            # set up SED look-up table
            nTemplates = len(extNames)

            self.sedLUT = np.zeros(nTemplates, dtype=[('TEMPLATE','i4'),
                                                      ('SYNTHMAG','f4',len(self.filterNames)),
                                                      ('FPRIME','f4',len(self.filterNames))])

            # now do it...looping is no problem since there aren't that many.

            for i in xrange(nTemplates):
                data = fits[extNames[i]].read()

                templateLambda = data['LAMBDA']
                templateFLambda = data['FLUX']
                templateFnu = templateFLambda * templateLambda * templateLambda

                parts=extNames[i].split('_')
                self.sedLUT['TEMPLATE'][i] = int(parts[1])

                # interpolate to atmLambda
                intFunc = interpolate.interp1d(templateLambda, templateFnu)
                fnu = np.zeros(self.atmLambda.size)
                good,=np.where((self.atmLambda >= templateLambda[0]) &
                               (self.atmLambda <= templateLambda[-1]))
                fnu[good] = intFunc(self.atmLambda[good])

                # out of range, let it hit the limit
                lo,=np.where(self.atmLambda < templateLambda[0])
                if (lo.size > 0):
                    fnu[lo] = intFunc(self.atmLambda[good[0]])
                hi,=np.where(self.atmLambda > templateLambda[-1])
                if (hi.size > 0):
                    fnu[hi] = intFunc(self.atmLambda[good[-1]])

                # compute synthetic mags
                for j in xrange(len(self.filterNames)):
                    num = integrate.simps(fnu * self.throughputs[j]['THROUGHPUT_AVG'][:] * self.atmStdTrans / self.atmLambda, self.atmLambda)
                    denom = integrate.simps(self.throughputs[j]['THROUGHPUT_AVG'][:] * self.atmStdTrans / self.atmLambda, self.atmLambda)

                    self.sedLUT['SYNTHMAG'][i,j] = -2.5*np.log10(num/denom)

                # and compute fprimes
                for j in xrange(len(self.filterNames)):
                    use,=np.where((templateLambda >= (self.lambdaStd[j]-delta)) &
                                  (templateLambda <= (self.lambdaStd[j]+delta)))

                    fit = np.polyfit(templateLambda[use] - self.lambdaStd[j],
                                     templateFnu[use],
                                     1)

                    self.sedLUT['FPRIME'][i,j] = fit[0] / fit[1]

            fits.close()

    def saveLUT(self,lutFile,clobber=False):
        """
        """

        import fitsio

        if (os.path.isfile(lutFile) and not clobber):
            self.fgcmLog.info("lutFile %s already exists, and clobber is False." % (lutFile))
            return

        self.fgcmLog.info("Saving LUT to %s" % (lutFile))

        # first, save the LUT itself
        fitsio.write(lutFile,self.lut.flatten(),extname='LUT',clobber=True)

        # and now save the indices
        maxFilterLen = len(max(self.filterNames, key=len))

        indexVals = np.zeros(1,dtype=[('FILTERNAMES', 'a%d' % (maxFilterLen), len(self.filterNames)),
                                      ('STDFILTERNAMES', 'a%d' % (maxFilterLen), len(self.stdFilterNames)),
                                      ('PMB','f8',self.pmb.size),
                                      ('PMBFACTOR','f8',self.pmb.size),
                                      ('PMBELEVATION','f8'),
                                      ('PWV','f8',self.pwv.size),
                                      ('O3','f8',self.o3.size),
                                      ('TAU','f8',self.tau.size),
                                      ('LAMBDANORM','f8'),
                                      ('ALPHA','f8',self.alpha.size),
                                      ('ZENITH','f8',self.zenith.size),
                                      ('NCCD','i4')])
        indexVals['FILTERNAMES'] = self.filterNames
        indexVals['STDFILTERNAMES'] = self.stdFilterNames
        indexVals['PMB'] = self.pmb
        indexVals['PMBFACTOR'] = self.pmbFactor
        indexVals['PMBELEVATION'] = self.pmbElevation
        indexVals['PWV'] = self.pwv
        indexVals['O3'] = self.o3
        indexVals['TAU'] = self.tau
        indexVals['LAMBDANORM'] = self.lambdaNorm
        indexVals['ALPHA'] = self.alpha
        indexVals['ZENITH'] = self.zenith
        indexVals['NCCD'] = self.nCCD

        fitsio.write(lutFile,indexVals,extname='INDEX')

        # and the standard values
        stdVals = np.zeros(1,dtype=[('PMBSTD','f8'),
                                    ('PWVSTD','f8'),
                                    ('O3STD','f8'),
                                    ('TAUSTD','f8'),
                                    ('ALPHASTD','f8'),
                                    ('ZENITHSTD','f8'),
                                    ('LAMBDARANGE','f8',2),
                                    ('LAMBDASTEP','f8'),
                                    ('LAMBDASTD','f8',len(self.filterNames)),
                                    ('LAMBDASTDFILTER','f8',len(self.filterNames)),
                                    ('LAMBDANORM','f8'),
                                    ('I0STD','f8',len(self.filterNames)),
                                    ('I1STD','f8',len(self.filterNames)),
                                    ('I10STD','f8',len(self.filterNames)),
                                    ('LAMBDAB','f8',len(self.filterNames)),
                                    ('ATMLAMBDA','f8',self.atmLambda.size),
                                    ('ATMSTDTRANS','f8',self.atmStdTrans.size)])
        stdVals['PMBSTD'] = self.pmbStd
        stdVals['PWVSTD'] = self.pwvStd
        stdVals['O3STD'] = self.o3Std
        stdVals['TAUSTD'] = self.tauStd
        stdVals['ALPHASTD'] = self.alphaStd
        stdVals['ZENITHSTD'] = self.zenithStd
        stdVals['LAMBDARANGE'] = self.lambdaRange
        stdVals['LAMBDASTEP'] = self.lambdaStep
        stdVals['LAMBDASTD'][:] = self.lambdaStd
        stdVals['LAMBDASTDFILTER'][:] = self.lambdaStdFilter
        stdVals['LAMBDANORM'][:] = self.lambdaNorm
        stdVals['I0STD'][:] = self.I0Std
        stdVals['I1STD'][:] = self.I1Std
        stdVals['I10STD'][:] = self.I10Std
        stdVals['LAMBDAB'][:] = self.lambdaB
        stdVals['ATMLAMBDA'][:] = self.atmLambda
        stdVals['ATMSTDTRANS'][:] = self.atmStdTrans

        fitsio.write(lutFile,stdVals,extname='STD')

        # and the derivatives
        self.fgcmLog.info("Writing Derivative LUT")
        fitsio.write(lutFile,self.lutDeriv.flatten(),extname='DERIV')

        # and the SED LUT
        if (self.makeSeds):
            self.fgcmLog.info("Writing SED LUT")
            fitsio.write(lutFile,self.sedLUT,extname='SED')

class FgcmLUT(object):
    """
    Class to hold the main throughput look-up table and apply it.  If loading from
     a fits table, initialize with initFromFits(lutFile).

    parameters
    ----------
    indexVals: numpy recarray
       With LUT index values
    lutFlat: numpy recarray
       Flattened I0/I1 arrays
    lutDerivFlat: numpy recarray
       Flattened I0/I1 derivative arrays
    stdVals: numpy recarray
       Standard atmosphere and associated values
    sedLUT: bool, default=False
       Use SED look-up table instead of colors (experimental).
    filterToBand: dict, optional
       Dictionary to map filterNames to bands if not unique
    """

    def __init__(self, indexVals, lutFlat, lutDerivFlat, stdVals, sedLUT=None, filterToBand=None):

        #self.filterNames = indexVals['FILTERNAMES'][0]
        #self.stdFilterNames = indexVals['STDFILTERNAMES'][0]
        self.filterNames = [n.decode('utf-8') for n in indexVals['FILTERNAMES'][0]]
        self.stdFilterNames = [n.decode('utf-8') for n in indexVals['STDFILTERNAMES'][0]]
        self.pmb = indexVals['PMB'][0]
        self.pmbFactor = indexVals['PMBFACTOR'][0]
        self.pmbDelta = self.pmb[1] - self.pmb[0]
        self.pmbElevation = indexVals['PMBELEVATION'][0]
        self.lambdaNorm = indexVals['LAMBDANORM'][0]

        self.pwv = indexVals['PWV'][0]
        self.pwvDelta = self.pwv[1] - self.pwv[0]
        self.o3 = indexVals['O3'][0]
        self.o3Delta = self.o3[1] - self.o3[0]
        self.tau = indexVals['TAU'][0]
        self.lnTau = np.log(self.tau)
        self.lnTauDelta = self.lnTau[1] - self.lnTau[0]
        self.alpha = indexVals['ALPHA'][0]
        self.alphaDelta = self.alpha[1] - self.alpha[0]
        self.zenith = indexVals['ZENITH'][0]
        self.secZenith = 1./np.cos(self.zenith*np.pi/180.)
        self.secZenithDelta = self.secZenith[1] - self.secZenith[0]
        self.nCCD = indexVals['NCCD'][0]
        self.nCCDStep = self.nCCD+1

        # make shared memory arrays for LUTs
        sizeTuple = (len(self.filterNames),self.pwv.size,self.o3.size,
                     self.tau.size,self.alpha.size,self.zenith.size,self.nCCDStep)

        self.lutI0Handle = snmm.createArray(sizeTuple,dtype='f4')
        snmm.getArray(self.lutI0Handle)[:,:,:,:,:,:,:] = lutFlat['I0'].reshape(sizeTuple)

        self.lutI1Handle = snmm.createArray(sizeTuple,dtype='f4')
        snmm.getArray(self.lutI1Handle)[:,:,:,:,:,:,:] = lutFlat['I1'].reshape(sizeTuple)

        # and read in the derivatives

        # create shared memory
        self.lutDPWVHandle = snmm.createArray(sizeTuple,dtype='f4')
        self.lutDO3Handle = snmm.createArray(sizeTuple,dtype='f4')
        self.lutDLnTauHandle = snmm.createArray(sizeTuple,dtype='f4')
        self.lutDAlphaHandle = snmm.createArray(sizeTuple,dtype='f4')
        self.lutDSecZenithHandle = snmm.createArray(sizeTuple,dtype='f4')

        self.lutDPWVI1Handle = snmm.createArray(sizeTuple,dtype='f4')
        self.lutDO3I1Handle = snmm.createArray(sizeTuple,dtype='f4')
        self.lutDLnTauI1Handle = snmm.createArray(sizeTuple,dtype='f4')
        self.lutDAlphaI1Handle = snmm.createArray(sizeTuple,dtype='f4')
        self.lutDSecZenithI1Handle = snmm.createArray(sizeTuple,dtype='f4')

        snmm.getArray(self.lutDPWVHandle)[:,:,:,:,:,:,:] = lutDerivFlat['D_PWV'].reshape(sizeTuple)
        snmm.getArray(self.lutDO3Handle)[:,:,:,:,:,:,:] = lutDerivFlat['D_O3'].reshape(sizeTuple)
        snmm.getArray(self.lutDLnTauHandle)[:,:,:,:,:,:,:] = lutDerivFlat['D_LNTAU'].reshape(sizeTuple)
        snmm.getArray(self.lutDAlphaHandle)[:,:,:,:,:,:,:] = lutDerivFlat['D_ALPHA'].reshape(sizeTuple)
        snmm.getArray(self.lutDSecZenithHandle)[:,:,:,:,:,:,:] = lutDerivFlat['D_SECZENITH'].reshape(sizeTuple)

        self.hasI1Derivatives = False
        try:
            snmm.getArray(self.lutDPWVI1Handle)[:,:,:,:,:,:,:] = lutDerivFlat['D_PWV_I1'].reshape(sizeTuple)
            snmm.getArray(self.lutDO3I1Handle)[:,:,:,:,:,:,:] = lutDerivFlat['D_O3_I1'].reshape(sizeTuple)
            snmm.getArray(self.lutDLnTauI1Handle)[:,:,:,:,:,:,:] = lutDerivFlat['D_LNTAU_I1'].reshape(sizeTuple)
            snmm.getArray(self.lutDAlphaI1Handle)[:,:,:,:,:,:,:] = lutDerivFlat['D_ALPHA_I1'].reshape(sizeTuple)
            snmm.getArray(self.lutDSecZenithI1Handle)[:,:,:,:,:,:,:] = lutDerivFlat['D_SECZENITH_I1'].reshape(sizeTuple)
            self.hasI1Derivatives = True
        except:
            # just fill with zeros
            pass
            #print("No I1 derivative information")

        # get the standard values

        self.pmbStd = stdVals['PMBSTD'][0]
        self.pwvStd = stdVals['PWVSTD'][0]
        self.o3Std = stdVals['O3STD'][0]
        self.tauStd = stdVals['TAUSTD'][0]
        self.lnTauStd = np.log(self.tauStd)
        self.alphaStd = stdVals['ALPHASTD'][0]
        self.zenithStd = stdVals['ZENITHSTD'][0]
        self.secZenithStd = 1./np.cos(np.radians(self.zenithStd))
        self.lambdaRange = stdVals['LAMBDARANGE'][0]
        self.lambdaStep = stdVals['LAMBDASTEP'][0]
        self.lambdaStd = stdVals['LAMBDASTD'][0]
        self.lambdaStdFilter = stdVals['LAMBDASTDFILTER'][0]
        self.I0Std = stdVals['I0STD'][0]
        self.I1Std = stdVals['I1STD'][0]
        self.I10Std = stdVals['I10STD'][0]
        self.lambdaB = stdVals['LAMBDAB'][0]
        self.atmLambda = stdVals['ATMLAMBDA'][0]
        self.atmStdTrans = stdVals['ATMSTDTRANS'][0]

        self.magConstant = 2.5/np.log(10)

        if (filterToBand is None):
            # just set up a 1-1 mapping
            self.filterToBand = {}
            for filterName in self.filterNames:
                self.filterToBand[filterName] = filterName
        else:
            self.filterToBand = filterToBand

        # finally, read in the sedLUT
        ## this is experimental
        self.hasSedLUT = False
        if (sedLUT is not None):
            self.sedLUT = sedLUT

            self.hasSedLUT = True

            ## FIXME: make general
            self.sedColor = self.sedLUT['SYNTHMAG'][:,0] - self.sedLUT['SYNTHMAG'][:,2]
            st = np.argsort(self.sedColor)

            self.sedColor = self.sedColor[st]
            self.sedLUT = self.sedLUT[st]

    @classmethod
    def initFromFits(cls, lutFile, filterToBand=None):
        """
        Initials FgcmLUT using fits file.

        parameters
        ----------
        lutFile: string
           Name of the LUT file
        """

        import fitsio

        lutFlat = fitsio.read(lutFile, ext='LUT')
        lutDerivFlat = fitsio.read(lutFile, ext='DERIV')
        indexVals = fitsio.read(lutFile, ext='INDEX')
        stdVals = fitsio.read(lutFile, ext='STD')

        try:
            sedLUT = fitsio.read(lutFile, ext='SED')
        except:
            sedLUT = None

        return cls(indexVals, lutFlat, lutDerivFlat, stdVals,
                   sedLUT=sedLUT, filterToBand=filterToBand)

    def getIndices(self, filterIndex, pwv, o3, lnTau, alpha, secZenith, ccdIndex, pmb):
        """
        Compute indices in the look-up table.  These are in regular (non-normalized) units.

        parameters
        ----------
        filterIndex: int array
           Array with values pointing to the filterName index
        pwv: float array
        o3: float array
        lnTau: float array
        alpha: float array
        secZenith: float array
        ccdIndex: int array
           Array with values point to the ccd index
        pmb: float array
        """

        return (filterIndex,
                np.clip(((pwv - self.pwv[0])/self.pwvDelta).astype(np.int32), 0,
                        self.pwv.size-1),
                np.clip(((o3 - self.o3[0])/self.o3Delta).astype(np.int32), 0,
                        self.o3.size-1),
                np.clip(((lnTau - self.lnTau[0])/self.lnTauDelta).astype(np.int32), 0,
                        self.lnTau.size-1),
                np.clip(((alpha - self.alpha[0])/self.alphaDelta).astype(np.int32), 0,
                        self.alpha.size-1),
                np.clip(((secZenith - self.secZenith[0])/self.secZenithDelta).astype(np.int32), 0,
                        self.secZenith.size-1),
                ccdIndex,
                (np.exp(-(pmb - self.pmbElevation)/self.pmbElevation)) ** 1.6)

    def computeI0(self, pwv, o3, lnTau, alpha, secZenith, pmb, indices):
        """
        Compute I0 from the look-up table.

        parameters
        ----------
        pwv: float array
        o3: float array
        lnTau: float array
        alpha: float array
        secZenith: float array
        pmb: float array
        indices: tuple, from getIndices()
        """

        # do a simple linear interpolation
        dPWV = pwv - (self.pwv[0] + indices[1] * self.pwvDelta)
        dO3 = o3 - (self.o3[0] + indices[2] * self.o3Delta)
        dlnTau = lnTau - (self.lnTau[0] + indices[3] * self.lnTauDelta)
        dAlpha = alpha - (self.alpha[0] + indices[4] * self.alphaDelta)
        dSecZenith = secZenith - (self.secZenith[0] + indices[5] * self.secZenithDelta)

        indicesSecZenithPlus = np.array(indices[:-1])
        indicesSecZenithPlus[5] += 1
        indicesPWVPlus = np.array(indices[:-1])
        indicesPWVPlus[1] = np.clip(indicesPWVPlus[1] + 1, 0, self.pwv.size-1)

        # also include cross-terms for tau and pwv
        # and a second-derivative term for pwv
        #  note that indices[-1] is the PMB vactor

        return indices[-1]*(snmm.getArray(self.lutI0Handle)[indices[:-1]] +
                            dPWV * snmm.getArray(self.lutDPWVHandle)[indices[:-1]] +
                            dO3 * snmm.getArray(self.lutDO3Handle)[indices[:-1]] +
                            dlnTau * snmm.getArray(self.lutDLnTauHandle)[indices[:-1]] +
                            dAlpha * snmm.getArray(self.lutDAlphaHandle)[indices[:-1]] +
                            dSecZenith * snmm.getArray(self.lutDSecZenithHandle)[indices[:-1]] +
                            dlnTau * dSecZenith * (snmm.getArray(self.lutDLnTauHandle)[tuple(indicesSecZenithPlus)] -
                                                   snmm.getArray(self.lutDLnTauHandle)[indices[:-1]])/self.secZenithDelta +
                            dPWV * dSecZenith * (snmm.getArray(self.lutDPWVHandle)[tuple(indicesSecZenithPlus)] -
                                                 snmm.getArray(self.lutDPWVHandle)[indices[:-1]])/self.secZenithDelta +
                            dPWV * (dPWV - self.pwvDelta) * (snmm.getArray(self.lutDPWVHandle)[tuple(indicesPWVPlus)] -
                                                             snmm.getArray(self.lutDPWVHandle)[indices[:-1]]))

    def computeI1(self, pwv, o3, lnTau, alpha, secZenith, pmb, indices):
        """
        Compute I1 from the look-up table.

        parameters
        ----------
        pwv: float array
        o3: float array
        lnTau: float array
        alpha: float array
        secZenith: float array
        pmb: float array
        indices: tuple, from getIndices()
        """

        # do a simple linear interpolation
        dPWV = pwv - (self.pwv[0] + indices[1] * self.pwvDelta)
        dO3 = o3 - (self.o3[0] + indices[2] * self.o3Delta)
        dlnTau = lnTau - (self.lnTau[0] + indices[3] * self.lnTauDelta)
        dAlpha = alpha - (self.alpha[0] + indices[4] * self.alphaDelta)
        dSecZenith = secZenith - (self.secZenith[0] + indices[5] * self.secZenithDelta)

        indicesSecZenithPlus = np.array(indices[:-1])
        indicesSecZenithPlus[5] += 1
        indicesPWVPlus = np.array(indices[:-1])
        indicesPWVPlus[1] = np.clip(indicesPWVPlus[1] + 1, 0, self.pwv.size-1)

        # also include a cross-term for tau
        #  note that indices[-1] is the PMB vactor

        return indices[-1]*(snmm.getArray(self.lutI1Handle)[indices[:-1]] +
                            dPWV * snmm.getArray(self.lutDPWVI1Handle)[indices[:-1]] +
                            dO3 * snmm.getArray(self.lutDO3I1Handle)[indices[:-1]] +
                            dlnTau * snmm.getArray(self.lutDLnTauI1Handle)[indices[:-1]] +
                            dAlpha * snmm.getArray(self.lutDAlphaI1Handle)[indices[:-1]] +
                            dSecZenith * snmm.getArray(self.lutDSecZenithI1Handle)[indices[:-1]] +
                            dlnTau * dSecZenith * (snmm.getArray(self.lutDLnTauI1Handle)[tuple(indicesSecZenithPlus)] -
                                                   snmm.getArray(self.lutDLnTauI1Handle)[indices[:-1]])/self.secZenithDelta +
                            dPWV * dSecZenith * (snmm.getArray(self.lutDPWVI1Handle)[tuple(indicesSecZenithPlus)] -
                                                 snmm.getArray(self.lutDPWVI1Handle)[indices[:-1]])/self.secZenithDelta +
                            dPWV * (dPWV - self.pwvDelta) * (snmm.getArray(self.lutDPWVI1Handle)[tuple(indicesPWVPlus)] -
                                                             snmm.getArray(self.lutDPWVI1Handle)[indices[:-1]]))

    def computeI1Old(self, indices):
        """
        Unused
        """
        return indices[-1] * snmm.getArray(self.lutI1Handle)[indices[:-1]]

    def computeLogDerivatives(self, indices, I0):
        """
        Compute log derivatives.  Used in FgcmChisq.

        parameters
        ----------
        indices: tuple, from getIndices()
        I0: float array, from computeI0()
        """

        # dL(i,j|p) = d/dp(2.5*log10(LUT(i,j|p)))
        #           = 1.086*(LUT'(i,j|p)/LUT(i,j|p))
        return (self.magConstant*snmm.getArray(self.lutDPWVHandle)[indices[:-1]] / I0,
                self.magConstant*snmm.getArray(self.lutDO3Handle)[indices[:-1]] / I0,
                self.magConstant*snmm.getArray(self.lutDLnTauHandle)[indices[:-1]] / (I0),
                self.magConstant*snmm.getArray(self.lutDAlphaHandle)[indices[:-1]] / I0)

    def computeLogDerivativesI1(self, indices, I0, I10, sedSlope):
        """
        Compute log derivatives for I1.  Used in FgcmChisq.

        parameters
        ----------
        indices: tuple, from getIndices()
        I0: float array, from computeI0()
        I10: float array, from computeI1()/computeI0()
        sedSlope: float array, fnuprime
        """

        # dL(i,j|p) += d/dp(2.5*log10((1+F'*I10^obs) / (1+F'*I10^std)))
        #  the std part cancels...
        #            = 1.086*(F'/(1+F'*I10)*((I0*LUT1' - I1*LUT0')/(I0^2))

        preFactor = (self.magConstant * (sedSlope / (1 + sedSlope*I10))) / I0**2.

        return (preFactor * (I0 * snmm.getArray(self.lutDPWVHandle)[indices[:-1]] -
                             I10 * I0 * snmm.getArray(self.lutDPWVI1Handle)[indices[:-1]]),
                preFactor * (I0 * snmm.getArray(self.lutDO3Handle)[indices[:-1]] -
                             I10 * I0 * snmm.getArray(self.lutDO3I1Handle)[indices[:-1]]),
                preFactor * (I0 * snmm.getArray(self.lutDLnTauHandle)[indices[:-1]] -
                             I10 * I0 * snmm.getArray(self.lutDLnTauI1Handle)[indices[:-1]]),
                preFactor * (I0 * snmm.getArray(self.lutDAlphaHandle)[indices[:-1]] -
                             I10 * I0 * snmm.getArray(self.lutDAlphaI1Handle)[indices[:-1]]))

    def computeSEDSlopes(self, objectSedColor):
        """
        Compute SED slopes using the SED look-up table.  Experimental.

        parameters
        ----------
        objectSedColor: float array
           Color used for SED look-up (typically g-i)
        """

        indices = np.clip(np.searchsorted(self.sedColor, objectSedColor),0,self.sedColor.size-2)
        # right now, a straight matching to the nearest sedColor (g-i)
        #  though I worry about this.
        #  in fact, maybe the noise will make this not work?  Or this is real?
        #  but the noise in g-r is going to cause things to bounce around.  Pout.

        return self.sedLUT['FPRIME'][indices,:]

    def computeStepUnits(self, stepUnitReference, stepGrain, meanNightDuration,
                         meanWashIntervalDuration, fitBands, bands, nCampaignNights):
        """
        Compute normalization factors for fit step units.  Note that this might need
         to be tweaked.

        parameters
        ----------
        stepUnitReference: float
           How much should a typical step move things?  0.001 mag is default.
        stepGrain: float
           Additional fudge factor to apply to all steps.
        meanNightDuration: float
           Mean duration of a night (days).
        meanWashIntervalDuration: float
           Mean duration between washes (days).
        fitBands: string array
           Which bands are used for the fit?
        bands: string array
           What are all the bands?
        nCampaignNights: int
           Total number of nights in observing campaign to be calibrated.
        """

        unitDict = {}

        # bigger unit, smaller step

        # compute tau units

        deltaMagLnTau = (2.5*np.log10(np.exp(-self.secZenithStd*np.exp(self.lnTauStd))) -
                         2.5*np.log10(np.exp(-self.secZenithStd*np.exp(self.lnTauStd+1.0))))

        unitDict['lnTauUnit'] = np.abs(deltaMagLnTau) / stepUnitReference / stepGrain
        unitDict['lnTauUnit'] /= 5.0

        # FIXME?
        unitDict['lnTauSlopeUnit'] = unitDict['lnTauUnit'] * meanNightDuration

        # look for first use of 'g' or 'r' band in filterToBand...
        #  this is the reference filter for tau/alpha

        alphaFilterIndex = -1
        for i,filterName in enumerate(self.filterNames):
            if (self.filterToBand[filterName] == 'g' or
                self.filterToBand[filterName] == 'r'):
                alphaFilterIndex = i
                break

        if alphaFilterIndex == -1:
            # We don't have anything here...
            # Just set this to 1.0, since it's not sensitive?
            unitDict['alphaUnit'] = 1.0 / stepUnitReference / stepGrain
        else:
            deltaMagAlpha = (2.5*np.log10(np.exp(-self.secZenithStd*self.tauStd*(self.lambdaStd[alphaFilterIndex]/self.lambdaNorm)**self.alphaStd)) -
                             2.5*np.log10(np.exp(-self.secZenithStd*self.tauStd*(self.lambdaStd[alphaFilterIndex]/self.lambdaNorm)**(self.alphaStd+1.0))))
            unitDict['alphaUnit'] = np.abs(deltaMagAlpha) / stepUnitReference / stepGrain

            # and scale these by fraction of bands affected...
            alphaNAffectedBands = 0
            for filterName in self.filterNames:
                if ((self.filterToBand[filterName] == 'u' and
                     'u' in fitBands) or
                    (self.filterToBand[filterName] == 'g' and
                     'g' in fitBands) or
                    (self.filterToBand[filterName] == 'r' and
                     'r' in fitBands)):
                    alphaNAffectedBands += 1

            unitDict['alphaUnit'] *= float(alphaNAffectedBands) / float(len(fitBands))

        # pwv units -- reference to z or y or Y
        pwvFilterIndex = -1
        for i,filterName in enumerate(self.filterNames):
            if (self.filterToBand[filterName] == 'z' or
                self.filterToBand[filterName] == 'y' or
                self.filterToBand[filterName] == 'Y'):
                pwvFilterIndex = i
                break

        if pwvFilterIndex == -1:
            unitDict['pwvUnit'] = 1.0 / stepUnitReference / stepGrain
        else:
            indicesStd = self.getIndices(pwvFilterIndex,self.pwvStd,self.o3Std,np.log(self.tauStd),self.alphaStd,self.secZenithStd,self.nCCD,self.pmbStd)
            i0Std = self.computeI0(self.pwvStd,self.o3Std,np.log(self.tauStd),self.alphaStd,self.secZenithStd,self.pmbStd,indicesStd)
            indicesPlus = self.getIndices(pwvFilterIndex,self.pwvStd+1.0,self.o3Std,np.log(self.tauStd),self.alphaStd,self.secZenithStd,self.nCCD,self.pmbStd)
            i0Plus = self.computeI0(self.pwvStd+1.0,self.o3Std,np.log(self.tauStd),self.alphaStd,self.secZenithStd,self.pmbStd,indicesPlus)
            deltaMagPWV = 2.5*np.log10(i0Std) - 2.5*np.log10(i0Plus)

            unitDict['pwvUnit'] = np.abs(deltaMagPWV) / stepUnitReference / stepGrain

            # scale by fraction of bands affected
            pwvNAffectedBands = 0
            for filterName in self.filterNames:
                if ((self.filterToBand[filterName] == 'z' and
                     'z' in fitBands) or
                    (self.filterToBand[filterName] == 'y' and
                     'y' in fitBands) or
                    (self.filterToBand[filterName] == 'Y' and
                     'Y' in fitBands)):
                    pwvNAffectedBands += 1
            unitDict['pwvUnit'] *= float(pwvNAffectedBands) / float(len(fitBands))

        # PWV percent slope units
        unitDict['pwvPerSlopeUnit'] = unitDict['pwvUnit'] * meanNightDuration * self.pwvStd

        # PWV Global step units
        unitDict['pwvGlobalUnit'] = unitDict['pwvUnit'] * nCampaignNights

        # O3 units -- reference to r
        o3FilterIndex = -1
        for i,filterName in enumerate(self.filterNames):
            if (self.filterToBand[filterName] == 'u' or
                self.filterToBand[filterName] == 'r'):
                o3FilterIndex = i
                break

        if o3FilterIndex == -1:
            unitDict['o3Unit'] = 1.0 / stepUnitReference / stepGrain
        else:
            indicesStd = self.getIndices(o3FilterIndex,self.pwvStd,self.o3Std,np.log(self.tauStd),self.alphaStd,self.secZenithStd,self.nCCD,self.pmbStd)
            i0Std = self.computeI0(self.pwvStd,self.o3Std,np.log(self.tauStd),self.alphaStd,self.secZenithStd,self.pmbStd,indicesStd)
            indicesPlus = self.getIndices(o3FilterIndex,self.pwvStd,self.o3Std+1.0,np.log(self.tauStd),self.alphaStd,self.secZenithStd,self.nCCD,self.pmbStd)
            i0Plus = self.computeI0(self.pwvStd,self.o3Std+1.0,np.log(self.tauStd),self.alphaStd,self.secZenithStd,self.pmbStd,indicesPlus)
            deltaMagO3 = 2.5*np.log10(i0Std) - 2.5*np.log10(i0Plus)

            unitDict['o3Unit'] = np.abs(deltaMagO3) / stepUnitReference / stepGrain

            # scale by fraction of bands that are affected
            o3NAffectedBands = 0
            for filterName in self.filterNames:
                if (self.filterToBand[filterName] == 'u' or
                    self.filterToBand[filterName] == 'r'):
                    o3NAffectedBands += 1
            unitDict['o3Unit'] *= float(o3NAffectedBands) / float(len(fitBands))

        # wash parameters units...
        unitDict['qeSysUnit'] = 1.0 / stepUnitReference / stepGrain
        unitDict['qeSysSlopeUnit'] = unitDict['qeSysUnit'] * meanWashIntervalDuration

        return unitDict


from __future__ import print_function

import sys
import os.path as osp
import timeit
import numpy as np

sys.path.insert(0, osp.dirname(osp.abspath(__file__)) + '/../')
from eval_reid import eval_func
"""
Test the speed of cython-based evaluation code. The speed improvements
can be much bigger when using the real reid data, which contains a larger
amount of query and gallery images.

Note: you might encounter the following error:
  'AssertionError: Error: all query identities do not appear in gallery'.
This is normal because the inputs are random numbers. Just try again.
"""

print('*** Compare running time ***')

setup = '''
import numpy as np
from eval_reid import eval_func
num_q = 30
num_g = 300
max_rank = 5
num_return = 200
distmat = np.random.rand(num_q, num_g) * 20
q_pids = np.random.randint(0, num_q, size=num_q)
g_pids = np.random.randint(0, num_g, size=num_g)
'''

pytime = timeit.timeit('eval_func(distmat, q_pids, g_pids, max_rank, num_return, use_cython=False)', setup=setup, number=20)
cytime = timeit.timeit('eval_func(distmat, q_pids, g_pids, max_rank, num_return, use_cython=True)', setup=setup, number=20)
print('Python time: {} s'.format(pytime))
print('Cython time: {} s'.format(cytime))
print('Cython is {} times faster than python\n'.format(pytime / cytime))

print("=> Check precision")

num_q = 30
num_g = 300
max_rank = 5
num_return = 200
distmat = np.random.rand(num_q, num_g) * 20
q_pids = np.random.randint(0, num_q, size=num_q)
g_pids = np.random.randint(0, num_g, size=num_g)

cmc, mAP, allAP = eval_func(distmat, q_pids, g_pids, max_rank, num_return, use_cython=False)
print("Python:\nmAP = {} \ncmc = {} \nallAP = {} \n".format(mAP, cmc, allAP))
cmc, mAP, allAP = eval_func(distmat, q_pids, g_pids, max_rank, num_return, use_cython=True)
print("Cython:\nmAP = {} \ncmc = {} \nallAP = {} \n".format(mAP, cmc, allAP))


#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import numpy as np
from keras.models import model_from_json
import array

N_neutrinos_in_channel = {
    "tt" : 2,
    "mt" : 3,
    "et" : 3,
    "mm" : 4,
    "em" : 4,
    "ee" : 4,
}

class DNN_model_from_json(object):

    def __init__(self, json_file):
        """ Load a DNN using the path to the json file. """
        # load json and create model
        NN_weights_path_and_file = json_file.split('/')
        NN_weights_path_and_file[-1] = "NN_weights-{}".format(NN_weights_path_and_file[-1].replace('.json', '.h5'))
        NN_weights_file = "/".join(NN_weights_path_and_file)

        json_file_ = open(json_file, 'r')
        loaded_model_json = json_file_.read()
        json_file_.close()
        loaded_model = model_from_json(loaded_model_json)

        # load weights into new model
        loaded_model.load_weights(NN_weights_file)
        print("Loaded DNN model from disk:")
        print("\t{}".format(json_file))

        self.model = loaded_model

        # load list of inputs for the model
        sys.path.insert(0, json_file.rstrip(json_file.split('/')[-1]))
        import inputs_for_models_in_this_dir
        reload(inputs_for_models_in_this_dir) # avoid being stuck with previous versions
        this_model_inputs = inputs_for_models_in_this_dir.inputs
        self.inputs = this_model_inputs

    def predict(self, evt, channel):
        """ Return the predicted value for an event in channel (tt, mt, et, mm, em, ee). """
        # Get the inputs from tree
        df = {}
        for input in self.inputs:
            if input not in ["mt_tt", "N_neutrinos_reco"]:
                df[input] = evt.GetLeaf(input).GetValue(0)
        # get mt_tt as defined for training
        df["mt_tt"] = (2*df["pt_1"]*df["pt_2"]*(1-np.cos(df["phi_1"]-df["phi_2"])))**.5
        # derive N neutrinos
        df["N_neutrinos_reco"] = N_neutrinos_in_channel[channel]
        # Set -10 to 0 for variable in ["jpt_r", "jeta_r", "jphi_r", "Njet_r"] as defined in training
        for variable in ["jpt_r", "jeta_r", "jphi_r", "Njet_r"]:
            if variable in self.inputs:
                if df[input] == -10:
                    df[input] = 0
        return self.model.predict(np.array([[df[input] for input in self.inputs]]))


from OpenGL.GL import *
from OpenGL.GLU import *
import glm
from pygl.shader import *
import numpy as np
from PIL import Image

class Skybox ():

    VBO = 0
    VAO = 0

    model = glm.mat4(1.0);
    view = glm.mat4(1.0);
    projection = glm.mat4(1.0);

    def __init__(self):

        self.shader = Shader(vertexPath='data/skyboxShader.vs', fragmentPath='data/skyboxShader.fs')

        self.vertices = np.array([
        # positions
        -1.0,  1.0, -1.0,
        -1.0, -1.0, -1.0,
         1.0, -1.0, -1.0,
         1.0, -1.0, -1.0,
         1.0,  1.0, -1.0,
        -1.0,  1.0, -1.0,
#
        -1.0, -1.0,  1.0,
        -1.0, -1.0, -1.0,
        -1.0,  1.0, -1.0,
        -1.0,  1.0, -1.0,
        -1.0,  1.0,  1.0,
        -1.0, -1.0,  1.0,
#
         1.0, -1.0, -1.0,
         1.0, -1.0,  1.0,
         1.0,  1.0,  1.0,
         1.0,  1.0,  1.0,
         1.0,  1.0, -1.0,
         1.0, -1.0, -1.0,
#
        -1.0, -1.0,  1.0,
        -1.0,  1.0,  1.0,
         1.0,  1.0,  1.0,
         1.0,  1.0,  1.0,
         1.0, -1.0,  1.0,
        -1.0, -1.0,  1.0,
#
        -1.0,  1.0, -1.0,
         1.0,  1.0, -1.0,
         1.0,  1.0,  1.0,
         1.0,  1.0,  1.0,
        -1.0,  1.0,  1.0,
        -1.0,  1.0, -1.0,
#
        -1.0, -1.0, -1.0,
        -1.0, -1.0,  1.0,
         1.0, -1.0, -1.0,
         1.0, -1.0, -1.0,
        -1.0, -1.0,  1.0,
         1.0, -1.0,  1.0],
         dtype=np.float32);

        #self.vertices = np.array([
        ##    -1.0, 1.0, 1.0,
        ##    1.0, 1.0, 1.0,
        ##    -1.0, -1.0, 1.0,
        ##    1.0, 1.0, 1.0,
        ##    -1.0, -1.0, 1.0,
        ##    1.0, -1.0, 1.0
        #1.0, -1.0, -1.0,
        # 1.0,  1.0, -1.0,
        #-1.0,  1.0, -1.0,
        #-1.0,  1.0, -1.0,
        #-1.0, -1.0, -1.0,
        # 1.0, -1.0, -1.0
        #], dtype=np.float32);

        self.VAO = glGenVertexArrays(1);
        self.VBO = glGenBuffers(1);

        glBindVertexArray(self.VAO);
        glBindBuffer(GL_ARRAY_BUFFER, self.VBO);
        glBufferData(GL_ARRAY_BUFFER, self.vertices.nbytes, self.vertices, GL_STATIC_DRAW);

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*self.vertices.dtype.itemsize, ctypes.c_void_p(0));
        glEnableVertexAttribArray(0);

        faces = [

            "./data/skybox/right.png",
            "./data/skybox/left.png",
            "./data/skybox/top.png",
            "./data/skybox/bottom.png",
            "./data/skybox/front.png",
            "./data/skybox/back.png"
        ]
        self.cubemapTexture = self.loadCubemap(faces);

        self.shader.use()
        self.shader.setInt("skybox", 0)

    def setMVP(self, model, view, projection):
        self.model = model;
        self.view = view;
        self.projection = projection;
        return 1;

    def draw(self):
        glDepthFunc(GL_LEQUAL);  # change depth function so depth test passes when values are equal to depth buffer's content
        self.shader.use();
        #
        #self.shader.setMat4("model", glm.value_ptr(self.model));
        self.shader.setMat4("view", glm.value_ptr(self.view));
        self.shader.setMat4("projection", glm.value_ptr(self.projection));
        # skybox cube
        glBindVertexArray(self.VAO);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_CUBE_MAP, self.cubemapTexture);
        #glDrawArrays(GL_POINTS, 0, 36);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);
        glDepthFunc(GL_LESS); # set depth function back to default
        return 1;

    def __del__(self):

        glDeleteVertexArrays(1, self.VAO);
        glDeleteBuffers(1, self.VBO);

    def loadCubemap(self, faces):

        textureID = 0;
        textureID = glGenTextures(1);
        glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

        for idx, name in enumerate(faces):
            img = Image.open(name) # .png, .bmp, etc. also work
            img_data = np.array(list(img.getdata()), np.int8)

            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + idx, 0, GL_RGBA, img.size[0], img.size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, img_data);
            print ('loading {}'.format(name))

        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

        return textureID;