#!/usr/bin/env python3
"""
helpers.py

Helpers functions for analyze_pahs.py
"""

import errno
import os
import pickle

import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
import numpy as np
from gaussfitter import onedgaussian, multigaussfit
from scipy.integrate import simps

from mattpy.utils import to_sigma, to_fwhm, quant_str

def ensure_exists(path):
    """Ensure the path exists; if not, make the directory."""
    try:
        os.makedirs(path)
    except OSError as exception:
        if exception.errno != errno.EEXIST:
            raise

def jy_to_si(flux_jy, wave):
    """Returns a flux array (converted from Jy to W/m^2/micron)."""
    flux_si = flux_jy * 3e-12 / wave**2
    return flux_si

def smooth(x, window_len=50, window='hanning'):
    """Returns a smoothed version of an array, from Stack Overflow."""
    if x.ndim != 1:
        raise ValueError("smooth only accepts 1 dimension arrays.")
    if x.size < window_len:
        raise ValueError("Input vector must be bigger than window size.")
    if window_len < 3:
        return x

    acceptable_windows = ['flat', 'hanning', 'hamming', 'bartlett', 'blackman']
    if window not in acceptable_windows:
        raise ValueError("Window must be in: ", str(acceptable_windows))

    s = np.r_[2 * x[0] - x[window_len - 1::-1], x,
              2 * x[-1] - x[-1:-window_len:-1]]

    if window == 'flat':
        w = np.ones(window_len, 'd')
    else:
        w = eval('np.' + window + '(window_len)')

    y = np.convolve(w / w.sum(), s, mode='same')

    return y[window_len:-window_len + 1]

def compute_feature_uncertainty(gposition, gsigma, wave_feat, rms):

    myrange = [gposition - (3. * gsigma), gposition + (3. * gsigma)]

    dl = wave_feat[1] - wave_feat[0]
    N = (myrange[1] - myrange[0]) / dl
    feature_uncertainty = (rms * np.sqrt(N) * dl * 2)

    return feature_uncertainty

def params_6gauss(basename, guess):

    p_non_aliphatics = {
        'params':
            [
                guess / 200.,  6.89, to_sigma(0.15),
                guess / 200.,  7.25, to_sigma(0.12),
                guess / 2.,  7.55, to_sigma(0.44),
                guess / 1.,  7.87, to_sigma(0.40),
                guess / 2.,  8.25, to_sigma(0.29),
                guess / 2.,  8.59, to_sigma(0.36),
            ],
        'limitedmin': [True] * 18,
        'limitedmax': [True] * 18,
        'fixed':
            [
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
            ],
        'minpars':
            [
                0.,          6.82,  to_sigma(0.06),
                0,           7.15,  to_sigma(0.05),
                0,           7.45,  to_sigma(0.315),
                0,           7.77,  to_sigma(0.275),
                0,           8.15,  to_sigma(0.165),
                0,           8.49,  to_sigma(0.235),
            ],
        'maxpars':
            [
                guess / 100., 6.96, to_sigma(0.21),
                guess / 100, 7.35, to_sigma(0.15),
                guess, 7.65, to_sigma(0.565),
                guess, 7.97, to_sigma(0.525),
                guess, 8.35, to_sigma(0.415),
                guess, 8.69, to_sigma(0.485),
            ]
    }

    p_non_aliphatics_xxoph = {
        'params':
            [
                0.,  6.89, to_sigma(0.15),
                0.,  7.25, to_sigma(0.12),
                guess / 2.,  7.55, to_sigma(0.44),
                guess / 1.,  7.87, to_sigma(0.40),
                guess / 2.,  8.25, to_sigma(0.29),
                guess / 2.,  8.59, to_sigma(0.36),
            ],
        'limitedmin': [True] * 18,
        'limitedmax': [True] * 18,
        'fixed':
            [
                True, False, False,
                True, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
            ],
        'minpars':
            [
                0.,          6.82,  to_sigma(0.06),
                0,           7.15,  to_sigma(0.05),
                0,           7.45,  to_sigma(0.315),
                0,           7.77,  to_sigma(0.275),
                0,           8.15,  to_sigma(0.165),
                0,           8.49,  to_sigma(0.235),
            ],
        'maxpars':
            [
                guess / 100., 6.96, to_sigma(0.21),
                guess / 100, 7.35, to_sigma(0.15),
                guess, 7.65, to_sigma(0.565),
                guess, 7.97, to_sigma(0.525),
                guess, 8.35, to_sigma(0.415),
                guess, 8.69, to_sigma(0.485),
            ]
    }

    p0 = {
        'params':
            [
                guess / 2.,  6.89, to_sigma(0.15),
                guess / 4.,  7.25, to_sigma(0.12),
                guess / 2.,  7.55, to_sigma(0.44),
                guess / 1.,  7.87, to_sigma(0.40),
                guess / 2.,  8.25, to_sigma(0.29),
                guess / 2.,  8.59, to_sigma(0.36),
            ],
        'limitedmin': [True] * 18,
        'limitedmax': [True] * 18,
        'fixed':
            [
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
            ],
        'minpars':
            [
                guess / 30., 6.82,  to_sigma(0.06),
                0,           7.15,  to_sigma(0.05),
                0,           7.45,  to_sigma(0.315),
                0,           7.77,  to_sigma(0.275),
                0,           8.15,  to_sigma(0.165),
                0,           8.49,  to_sigma(0.235),
            ],
        'maxpars':
            [
                guess, 6.96, to_sigma(0.21),
                guess, 7.35, to_sigma(0.15),
                guess, 7.65, to_sigma(0.565),
                guess, 7.97, to_sigma(0.525),
                guess, 8.35, to_sigma(0.415),
                guess, 8.69, to_sigma(0.485),
            ]
    }

    p1 = {
        'params':
            [
                guess / 2.,  6.89, to_sigma(0.15),
                guess / 4.,  7.25, to_sigma(0.12),
                guess / 2.,  7.55, to_sigma(0.44),
                guess / 1.,  7.87, to_sigma(0.40),
                guess / 2.,  8.25, to_sigma(0.29),
                guess / 2.,  8.59, to_sigma(0.36),
            ],
        'limitedmin': [True] * 18,
        'limitedmax': [True] * 18,
        'fixed':
            [
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
            ],
        'minpars':
            [
                guess / 30., 6.82,  to_sigma(0.06),
                guess / 40., 7.15,  to_sigma(0.05),
                guess / 30., 7.45,  to_sigma(0.315),
                guess / 30., 7.77,  to_sigma(0.275),
                0,           8.15,  to_sigma(0.165),
                guess / 30., 8.49,  to_sigma(0.235),
            ],
        'maxpars':
            [
                guess, 6.96, to_sigma(0.21),
                guess, 7.35, to_sigma(0.15),
                guess, 7.65, to_sigma(0.565),
                guess, 7.97, to_sigma(0.525),
                guess, 8.35, to_sigma(0.415),
                guess, 8.69, to_sigma(0.485),
            ]
    }

    p2 = {
        'params':
            [
                guess / 2.,  6.89, to_sigma(0.15),
                0., 7.25, to_sigma(0.12),
                guess / 2.,  7.55, to_sigma(0.44),
                guess / 1.,  7.87, to_sigma(0.40),
                guess / 2.,  8.25, to_sigma(0.29),
                guess / 2.,  8.59, to_sigma(0.36),
            ],
        'limitedmin': [True] * 18,
        'limitedmax': [True] * 18,
        'fixed':
            [
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
            ],
        'minpars':
            [
                guess / 30., 6.82,  to_sigma(0.06),
                0.,          7.15,  to_sigma(0.05),
                guess / 30., 7.45,  to_sigma(0.315),
                guess / 30., 7.77,  to_sigma(0.275),
                0.,          8.15,  to_sigma(0.165),
                guess / 30., 8.49,  to_sigma(0.235),
            ],
        'maxpars':
            [
                guess, 6.96, to_sigma(0.21),
                guess, 7.35, to_sigma(0.15),
                guess, 7.65, to_sigma(0.565),
                guess, 7.97, to_sigma(0.525),
                guess, 8.35, to_sigma(0.415),
                guess, 8.69, to_sigma(0.485),
            ]
    }

    p3 = {
        'params':
            [
                guess / 2.,  6.89, to_sigma(0.15),
                guess / 4., 7.25, to_sigma(0.12),
                guess / 2.,  7.55, to_sigma(0.44),
                guess / 1.,  7.87, to_sigma(0.40),
                guess / 2.,  8.25, to_sigma(0.29),
                guess / 2.,  8.59, to_sigma(0.36),
            ],
        'limitedmin': [True] * 18,
        'limitedmax': [True] * 18,
        'fixed':
            [
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
            ],
        'minpars':
            [
                guess / 30., 6.82,  to_sigma(0.06),
                guess / 30., 7.15,  to_sigma(0.05),
                guess / 30., 7.45,  to_sigma(0.315),
                guess / 30., 7.77,  to_sigma(0.275),
                guess / 30., 8.15,  to_sigma(0.165),
                guess / 30., 8.49,  to_sigma(0.235),
            ],
        'maxpars':
            [
                guess, 6.96, to_sigma(0.21),
                guess, 7.35, to_sigma(0.15),
                guess, 7.65, to_sigma(0.565),
                guess, 7.97, to_sigma(0.525),
                guess, 8.35, to_sigma(0.415),
                guess, 8.69, to_sigma(0.485),
            ]
    }

    p4 = {
        'params':
            [
                guess / 2.,  6.89, to_sigma(0.15),
                guess / 6.,  7.25, to_sigma(0.12),
                guess / 2.,  7.55, to_sigma(0.44),
                guess / 1.,  7.87, to_sigma(0.40),
                guess / 2.,  8.25, to_sigma(0.29),
                guess / 2.,  8.59, to_sigma(0.36),
            ],
        'limitedmin': [True] * 18,
        'limitedmax': [True] * 18,
        'fixed':
            [
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
            ],
        'minpars':
            [
                guess / 30., 6.82,  to_sigma(0.06),
                guess / 30., 7.15,  to_sigma(0.05),
                guess / 30., 7.45,  to_sigma(0.315),
                guess / 30., 7.77,  to_sigma(0.275),
                0.,          8.15,  to_sigma(0.165),
                guess / 30., 8.49,  to_sigma(0.235),
            ],
        'maxpars':
            [
                guess, 6.96, to_sigma(0.21),
                guess, 7.35, to_sigma(0.15),
                guess, 7.65, to_sigma(0.565),
                guess, 7.97, to_sigma(0.525),
                guess, 8.35, to_sigma(0.415),
                guess, 8.69, to_sigma(0.485),
            ]
    }

    p5 = {
        'params':
            [
                guess / 2.,  6.89, to_sigma(0.15),
                1.21852599e-15 * 0.1,  7.25, to_sigma(0.1),
                guess / 2.,  7.55, to_sigma(0.44),
                guess / 1.,  7.87, to_sigma(0.40),
                guess / 2.,  8.25, to_sigma(0.29),
                guess / 2.,  8.59, to_sigma(0.36),
            ],
        'limitedmin': [True] * 18,
        'limitedmax': [True] * 18,
        'fixed':
            [
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
            ],
        'minpars':
            [
                guess / 30., 6.82,  to_sigma(0.06),
                0., 7.15,  to_sigma(0.05),
                guess / 30., 7.45,  to_sigma(0.315),
                guess / 30., 7.77,  to_sigma(0.275),
                0.,          8.15,  to_sigma(0.165),
                guess / 30., 8.49,  to_sigma(0.235),
            ],
        'maxpars':
            [
                guess, 6.96, to_sigma(0.21),
                1.21852599e-15 * 0.25, 7.35, to_sigma(0.13),
                guess, 7.65, to_sigma(0.565),
                guess, 7.97, to_sigma(0.525),
                guess, 8.35, to_sigma(0.415),
                guess, 8.69, to_sigma(0.485),
            ]
    }

    p6 = {
        'params':
            [
                guess / 2.,  6.93, to_sigma(0.15),
                guess / 6.,  7.25, to_sigma(0.12),
                guess / 2.,  7.55, to_sigma(0.40),
                guess / 1.,  7.87, to_sigma(0.40),
                guess / 2.,  8.25, to_sigma(0.29),
                guess / 2.,  8.59, to_sigma(0.36),
            ],
        'limitedmin': [True] * 18,
        'limitedmax': [True] * 18,
        'fixed':
            [
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
            ],
        'minpars':
            [
                guess / 30., 6.91,  to_sigma(0.06),
                0., 7.15,  to_sigma(0.05),
                guess / 30., 7.53,  to_sigma(0.315),
                guess / 30., 7.77,  to_sigma(0.275),
                0.,          8.15,  to_sigma(0.165),
                guess / 30., 8.49,  to_sigma(0.235),
            ],
        'maxpars':
            [
                guess, 6.96, to_sigma(0.16),
                guess, 7.35, to_sigma(0.15),
                guess, 7.65, to_sigma(0.41),
                guess, 7.97, to_sigma(0.525),
                guess, 8.35, to_sigma(0.415),
                guess, 8.69, to_sigma(0.485),
            ]
    }

    p7 = {
        'params':
            [
                guess / 2.,  6.89, to_sigma(0.15),
                guess / 6.,  7.25, to_sigma(0.12),
                guess / 2.,  7.55, to_sigma(0.44),
                guess / 1.,  7.87, to_sigma(0.40),
                guess / 2.,  8.25, to_sigma(0.29),
                guess / 2.,  8.59, to_sigma(0.36),
            ],
        'limitedmin': [True] * 18,
        'limitedmax': [True] * 18,
        'fixed':
            [
            False, False, False,
            False, False, False,
            False, False, False,
            False, False, False,
            False, False, False,
            False, False, False,
            ],
        'minpars':
            [
                guess / 30., 6.82,  to_sigma(0.06),
                guess / 30., 7.15,  to_sigma(0.05),
                guess / 30., 7.45,  to_sigma(0.315),
                guess / 30., 7.77,  to_sigma(0.275),
                0.,          8.15,  to_sigma(0.165),
                guess / 30., 8.49,  to_sigma(0.235),
            ],
        'maxpars':
            [
                guess, 6.96, to_sigma(0.21),
                guess, 7.35, to_sigma(0.15),
                guess, 7.65, to_sigma(0.565),
                guess, 7.97, to_sigma(0.525),
                guess, 8.35, to_sigma(0.415),
                guess, 8.69, to_sigma(0.485),
            ]
    }

    p8 = {
        'params':
            [
                guess / 2.,  6.89, to_sigma(0.15),
                guess / 6.,  7.25, to_sigma(0.12),
                guess / 2.,  7.60, to_sigma(0.44),
                guess / 1.,  7.87, to_sigma(0.40),
                guess / 2.,  8.25, to_sigma(0.29),
                guess / 2.,  8.59, to_sigma(0.36),
            ],
        'limitedmin': [True] * 18,
        'limitedmax': [True] * 18,
        'fixed':
            [
            False, False, False,
            False, False, False,
            False, False, False,
            False, False, False,
            False, False, False,
            False, False, False,
            ],
        'minpars':
            [
                guess / 30., 6.82,  to_sigma(0.06),
                guess / 30., 7.15,  to_sigma(0.05),
                guess / 30., 7.45,  to_sigma(0.315),
                guess / 30., 7.77,  to_sigma(0.275),
                guess / 30., 8.15,  to_sigma(0.165),
                guess / 30., 8.49,  to_sigma(0.235),
            ],
        'maxpars':
            [
                guess, 6.96, to_sigma(0.21),
                guess, 7.35, to_sigma(0.15),
                guess, 7.65, to_sigma(0.565),
                guess, 7.97, to_sigma(0.525),
                guess, 8.35, to_sigma(0.415),
                guess, 8.69, to_sigma(0.485),
            ]
    }

    p9 = {
        'minpars':
            [
                guess / 30., 6.82, to_sigma(0.06),
                0.,          7.15, to_sigma(0.05),
                guess / 30., 7.45, to_sigma(0.315),
                guess / 30., 7.77, to_sigma(0.275),
                0.,          8.15, to_sigma(0.165),
                guess / 30., 8.49, to_sigma(0.235),
            ],
        'params':
            [
                guess / 2.,  6.89, to_sigma(0.15),
                guess / 6.,  7.25, to_sigma(0.12),
                guess / 2.,  7.55, to_sigma(0.44),
                guess / 1.,  7.87, to_sigma(0.40),
                guess / 2.,  8.25, to_sigma(0.29),
                guess / 2.,  8.59, to_sigma(0.36),
            ],
        'maxpars':
            [
                guess,       6.96, to_sigma(0.21),
                guess,       7.35, to_sigma(0.15),
                guess,       7.65, to_sigma(0.565),
                guess,       7.97, to_sigma(0.525),
                guess,       8.35, to_sigma(0.415),
                guess,       8.69, to_sigma(0.485),
            ],
        'limitedmin': [True] * 18,
        'limitedmax': [True] * 18,
        'fixed':
            [
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
            ]
    }

    p10 = {
        'params':
            [
                guess / 2.,  6.89, to_sigma(0.15),
                guess / 4.,  7.25, to_sigma(0.12),
                guess / 2.,  7.55, to_sigma(0.44),
                guess / 1.,  7.87, to_sigma(0.40),
                guess / 2.,  8.25, to_sigma(0.29),
                guess / 2.,  8.59, to_sigma(0.36),
            ],
        'limitedmin': [True] * 18,
        'limitedmax': [False] * 18,
        'fixed':
            [
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
            ],
        'minpars':
            [
                guess / 30., 6.82,  to_sigma(0.06),
                guess / 40., 7.22,  to_sigma(0.07),
                guess / 30., 7.45,  to_sigma(0.315),
                guess / 30., 7.77,  to_sigma(0.275),
                0,           8.15,  to_sigma(0.165),
                guess / 30., 8.49,  to_sigma(0.235),
            ],
        'maxpars':
            [
                guess, 6.96, to_sigma(0.16),
                guess, 7.32, to_sigma(0.15),
                guess, 7.65, to_sigma(0.6),
                guess, 7.97, to_sigma(0.525),
                guess, 8.35, to_sigma(0.415),
                guess, 8.69, to_sigma(0.485),
            ]
    }

    p11 = {
        'params':
            [
                guess / 2.,  6.89, to_sigma(0.15),
                guess / 4.,  7.25, to_sigma(0.12),
                guess / 100.,  7.55, to_sigma(0.44),
                guess / 1.,  7.87, to_sigma(0.40),
                guess / 2.,  8.25, to_sigma(0.29),
                guess / 2.,  8.59, to_sigma(0.36),
            ],
        'limitedmin': [True] * 18,
        'limitedmax': [True] * 18,
        'fixed':
            [
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
            ],
        'minpars':
            [
                guess / 30., 6.82,  to_sigma(0.06),
                0,           7.15,  to_sigma(0.05),
                0,           7.45,  to_sigma(0.315),
                0,           7.77,  to_sigma(0.275),
                0,           8.15,  to_sigma(0.165),
                0,           8.49,  to_sigma(0.235),
            ],
        'maxpars':
            [
                guess, 6.96, to_sigma(0.21),
                guess, 7.35, to_sigma(0.15),
                guess / 98., 7.65, to_sigma(0.565),
                guess, 7.97, to_sigma(0.525),
                guess, 8.35, to_sigma(0.415),
                guess, 8.69, to_sigma(0.485),
            ]
    }

    p12 = {
        'params':
            [
                guess / 2.,  6.89, to_sigma(0.15),
                guess / 4.,  7.25, to_sigma(0.12),
                guess / 2.,  7.55, to_sigma(0.44),
                guess / 1.,  7.87, to_sigma(0.40),
                guess / 2.,  8.25, to_sigma(0.29),
                guess / 2.,  8.59, to_sigma(0.36),
            ],
        'limitedmin': [True] * 18,
        'limitedmax': [True] * 18,
        'fixed':
            [
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
                False, False, False,
            ],
        'minpars':
            [
                guess / 30., 6.82,  to_sigma(0.06),
                guess / 40., 7.15,  to_sigma(0.05),
                guess / 30., 7.45,  to_sigma(0.315),
                guess / 30., 7.77,  to_sigma(0.275),
                0,           8.15,  to_sigma(0.165),
                guess / 30., 8.49,  to_sigma(0.235),
            ],
        'maxpars':
            [
                guess, 6.96, to_sigma(0.21),
                guess, 7.35, to_sigma(0.15),
                guess, 7.65, to_sigma(0.565),
                guess, 7.97, to_sigma(0.525),
                guess, 8.35, to_sigma(0.415),
                guess, 8.69, to_sigma(0.485),
            ]
    }

    param_dict = {
        'hd97048_convCWsub': p0,         # GOOD, wouldn't trust 72 tho
        'hd135344_convCWsub': p11,        # * NO ALIPHATICS TRUSTED!!! *
        'IRAS05063_CWsub': p3,           # GOOD
        'IRAS05092_CWsub': p0,           # GOOD
        'IRAS05186_CWsub': p0,           # GOOD
        'IRAS05361_CWsub': p0,           # GOOD. TRY LIMIT G7.6 FROM LEFT?
        'IRAS05370_CWsub': p4,           # GOOD, don't trust 7.2
        'IRAS05413_CWsub': p2,           # GOOD? ONLY TRUST 6.9, maybe 77 flux
        'IRAS05588_CWsub': p0,           # GOOD
        'IRAS06111_CWsub': p0,           # GOOD
        'IRAS14429_CWsub': p0,           # GOOD
        'IRAS15482_CWsub': p5,           # GOOD, don't trust 7.2 maybe (manual)
        'iras17047_SWS_CWsub': p10,      # GOOD, had to do ct myself
        'IRASF05110-6616_LR_CWsub': p0,  # GOOD
        'IRASf05192_CWsub': p1,          # GOOD, quesitonable 69/72. tho
        'J004441_CWsub': p0,             # GOOD
        'J010546_CWsub': p6,             # GOOD, not perfect but good enough?
        'j050713_CWsub': p7,             # GOOD
        'J052043_CWsub': p8,             # GOOD, had to drop errors?
        'J052520_CWsub': p1,             # GOOD
        'NGC1978WBT2665_CWsub': p1,      # GOOD
        'SMPLMC076_CWsub': p12,           # new
        'SMPSMC006_CWsub': p9,           # GOOD, dropping fluxerr in fit (!!)
        'SMPSMC011_CWsub': p1,           # GOOD

        'xxOph_SWS_CWsub': p_non_aliphatics_xxoph,
    }

    if basename in param_dict:
        print('In standard PARAM DICT.')
        parameters = param_dict[basename]
    else:
        parameters = p_non_aliphatics

    return parameters

    # TO DO: UNCERTAINTIES!!!

    # pos, flux, sigma = line69_params[basename]
    # amp = flux / (np.sqrt(2) * np.abs(sigma) * np.sqrt(np.pi))

    # max_flux72 = 0.35 * flux
    # amp_72_approx = max_flux72 / (np.sqrt(2) * np.abs(sigma)* np.sqrt(np.pi))

    # p0['params'][0] = amp
    # p0['params'][1] = pos
    # p0['params'][2] = sigma

    # p0['fixed'][0] = True
    # p0['fixed'][1] = True
    # p0['fixed'][2] = True

    # p0['maxpars'][3] = amp_72_approx
    # p0['params'][3] = amp_72_approx * 0.5

def measure_112_RMS(wave, csub):
    xmin = 11.9
    xmax = 12.1

    myrange = np.where((wave >= xmin) & (wave <= xmax))
    csub_mr = csub[myrange]
    rms = np.sqrt(np.mean(csub_mr**2))

    return rms  # , xmin, xmax

def fit_aliphatics(basename, wave, flux, fluxerr, rms, output_dir):

    def fit_straight_line(wave, flux, fluxerr):

        # Section 1: Fit Aliphatics
        # Define wavelength range of relevance
        lim = np.where((wave > 6.6) & (wave < 7.65))
        waveLim = wave[lim]
        fluxLim = flux[lim]
        errLim = fluxerr[lim]

        # Region where 7.2 is found
        lim2 = np.where((wave > 6.7) & (wave < 7.6))
        waveLim2 = wave[lim2]
        fluxLim2 = flux[lim2]
        # errLim2 = fluxerr[lim2]

        # Draw a straight line under 7.2 feature
        # Comment section out if no 7.2

        winDX = np.where((waveLim2 >= 7.) & (waveLim2 <= 7.2)
                         )  # Find the first ancor point
        winWave = waveLim2[winDX]
        winFlux = fluxLim2[winDX]
        anchor1Wave = winWave[np.nanargmin(winFlux)]
        anchor1Flux = np.nanmin(winFlux)

        winDX = np.where((waveLim2 >= 7.5))  # Find the second anchor point
        winWave = waveLim2[winDX]
        winFlux = fluxLim2[winDX]
        anchor2Wave = winWave[np.nanargmin(winFlux)]
        anchor2Flux = np.nanmin(winFlux)

        # Define the straight line from the anchor points
        x = np.array([anchor1Wave, anchor2Wave])
        y = np.array([anchor1Flux, anchor2Flux])
        StrLine = np.polyfit(x, y, deg=1)  # Fit straight line
        StrLineFit = StrLine[0] * waveLim2 + StrLine[1]

        # Plot straight line to check
        fig1, ax = plt.subplots()
        ax.plot(waveLim, fluxLim, '-r', lw=2)  # Make figure
        ax.errorbar(
            waveLim,
            fluxLim,
            errLim,
            color='r',
            ecolor='0.45',
            lw=2,
            elinewidth=1)
        ax.plot(waveLim2, StrLineFit, 'g-', label='7.2 Cont', lw=2)
        ax.plot(x, y, 'bo')

        ax.legend(loc=0, fontsize='small')
        ax.set_xlabel('Wavelength (microns)')
        ax.set_ylabel('Flux (W/m^2)')
        # ax.set_title(fnameStr + ' -- Line fit')
        ax.grid()
        ax.minorticks_on()

        ensure_exists(output_dir)
        pdf_filename = output_dir + basename + '_aliphatic_fit_1.pdf'
        print('Saved: ', pdf_filename)
        fig1.savefig(pdf_filename, format='pdf', bbox_inches='tight')

        plt.close()
        fig1.clear()

        if StrLine[0] > 0:  # Subtract straight line from data

            lim69 = np.where(waveLim < x[0])  # Create limits of subtraction
            lim72 = np.where((waveLim >= x[0]) & (waveLim <= x[1]))
            limOver = np.where(waveLim > x[1])

            flux69 = fluxLim[lim69]
            # Subtraction
            flux72 = fluxLim[lim72] - \
                (StrLine[0] * waveLim[lim72] + StrLine[1])
            fluxOver = fluxLim[limOver]

            fluxFull = []
            fluxFull = np.append(flux69, flux72)
            fluxFull = np.append(fluxFull, fluxOver)  # Create array

        else:
            fluxFull = fluxLim

        return waveLim, fluxFull, errLim

    def fit_gaussians(waveLim, fluxFull, errLim, fnameStr='temp_label'):

        # End of commented section if no 7.2
        # fluxFull = fluxLim # Comment if spectrum has 7.2 feature

        # Fit Gaussian functions to peaks
        # Change ngauss and parameters as needed

        # fitAli = multigaussfit(
        #     waveLim, fluxFull, ngauss=2, err=errLim,
        #     params=[0.12e-15,6.85,0.1,0.5e-16,7.23,0.05],
        #     limitedmin=[True,True,True,True,True,True],
        #     limitedmax=[True,True,True,True,True,True],
        #     minpars=[0.01e-18,6.8,0.03,0,7,0],
        #     maxpars=[1.5e-14,6.86,0.2,0.5e-15,7.3,0.06]
        # )

        fitAli = multigaussfit(
            waveLim, fluxFull, ngauss=2, err=errLim, params=[
                0.2e-14, 6.9, 0.1, 0.17e-14, 7.23, 0.05], limitedmin=[
                True, True, True, True, True, True], minpars=[
                0, 6.8, 0.04, 0, 7, 0], limitedmax=[
                    True, True, True, True, True, True], maxpars=[
                        0.5e-13, 7, 0.2, 0.1e-13, 7.25, 0.2])

        # Plot fit
        fig2, ax = plt.subplots()

        ax.plot(waveLim, fluxFull, '-r', label=fnameStr, lw=2)
        ax.errorbar(
            waveLim,
            fluxFull,
            errLim,
            color='r',
            ecolor='0.45',
            lw=2,
            elinewidth=1)
        ax.plot(waveLim, fitAli[1], '-g', label='Spectral Fit', lw=1.5)
        ax.fill_between(waveLim, fitAli[1], facecolor='green', alpha=0.15)
        ax.axhline(y=0, color='k', ls='-', zorder=-10, lw=2)

        ax.legend(loc=0, fontsize='small')
        ax.set_xlabel('Wavelength (microns)')
        ax.set_ylabel('Flux (W/m^2)')
        ax.set_title(fnameStr + ' -- Gaussian Fit')
        ax.grid()
        ax.minorticks_on()

        ensure_exists(output_dir)
        pdf_filename = output_dir + basename + '_aliphatic_fit_2.pdf'
        print('Saved: ', pdf_filename)
        fig2.savefig(pdf_filename, format='pdf', bbox_inches='tight')

        plt.close()
        fig2.clear()

        # print('Fit parameters for aliphatic features:')
        # print(fitAli[0])

        return fitAli

    def compute_aliphatic_fluxes(fitAli, waveLim, rms):

        # Calculate integrated flux of aliphatic features from fit
        Gauss69 = fitAli[0][0] * \
            np.exp(-(waveLim - fitAli[0][1])**2 / (2 * fitAli[0][2]**2))
        area69 = simps(Gauss69, waveLim)

        Gauss72 = fitAli[0][3] * \
            np.exp(-(waveLim - fitAli[0][4])**2 / (2 * fitAli[0][5]**2))
        area72 = simps(Gauss72, waveLim)

        err69 = compute_feature_uncertainty(
            fitAli[0][1], fitAli[0][2], waveLim, rms)
        err72 = compute_feature_uncertainty(
            fitAli[0][4], fitAli[0][5], waveLim, rms)

        # fitErr69 = np.sqrt((fitAli[2][0]/fitAli[0][0])**2 + \
        #     (fitAli[2][2]/fitAli[0][2])**2) * area69
        # fitErr72 = np.sqrt((fitAli[2][3]/fitAli[0][3])**2 + \
        #     (fitAli[2][5]/fitAli[0][5])**2) * area72

        errTot69 = err69  # + fitErr69
        errTot72 = err72  # + fitErr72

        # print('Integrated fluxes of aliphatics:')
        # print(area69, area72)

        SNR69 = area69 / errTot69
        SNR72 = area72 / errTot72

        # print('S/N of aliphatics: ', SNR69, SNR72)

        return area69, err69, SNR69, area72, err72, SNR72

    waveLim, fluxFull, errLim = fit_straight_line(wave, flux, fluxerr)

    fitAli = fit_gaussians(waveLim, fluxFull, errLim, fnameStr='temp_label')

    area69, err69, SNR69, area72, err72, SNR72 = \
        compute_aliphatic_fluxes(fitAli, waveLim, rms)

    return fitAli, waveLim, area69, SNR69, area72, SNR72

def fit_aromatics(basename, wave, flux, fluxerr, rms, output_dir):

    def fit_straight_line(wave, flux, fluxerr, fnameStr='temp_label'):

        # Section 2: Fit 7.7
        # Limits of feature - change as needed
        lim77 = np.where((wave >= 6.9) & (wave <= 9))
        waveLim77 = wave[lim77]
        fluxLim77 = flux[lim77]
        errLim77 = fluxerr[lim77]

        # Limit for 7.2 feature - change as needed
        lim2_a = np.where((wave > 6.9) & (wave < 7.45))
        waveLim2_a = wave[lim2_a]
        fluxLim2_a = flux[lim2_a]
        # errLim2_a = fluxerr[lim2_a]

        # Draw a straight line under 7.2 feature
        # Comment section out if no 7.2

        winDX77 = np.where((waveLim2_a >= 7.) & (waveLim2_a <= 7.2))
        winWave77 = waveLim2_a[winDX77]
        winFlux77 = fluxLim2_a[winDX77]
        anchor1Wave77 = winWave77[np.nanargmin(winFlux77)]
        anchor1Flux77 = np.nanmin(winFlux77)

        winDX77 = np.where((waveLim2_a >= 7.2))
        winWave77 = waveLim2_a[winDX77]
        winFlux77 = fluxLim2_a[winDX77]
        anchor2Wave77 = winWave77[np.nanargmin(winFlux77)]
        anchor2Flux77 = np.nanmin(winFlux77)

        # Define the straight line from the anchor points
        x77 = np.array([anchor1Wave77, anchor2Wave77])
        y77 = np.array([anchor1Flux77, anchor2Flux77])
        StrLine77 = np.polyfit(x77, y77, deg=1)  # Fit straight line
        StrLineFit77 = StrLine77[0] * waveLim2_a + StrLine77[1]

        # Comment out section if no 7.2
        fig3, ax = plt.subplots()  # Define figure
        ax.plot(waveLim77, fluxLim77, '-r', label=fnameStr, lw=2)
        ax.errorbar(
            waveLim77,
            fluxLim77,
            errLim77,
            color='r',
            ecolor='0.45',
            lw=2,
            elinewidth=1)
        # Plot straight line - comment out if no 7.2 feature
        ax.plot(waveLim2_a, StrLineFit77, 'g-', lw=2)

        ax.axhline(y=0, color='k', ls='-', zorder=-10, lw=2)
        ax.legend(loc=0, fontsize='small')
        ax.grid()
        ax.minorticks_on()

        ax.set_title(fnameStr + ' 7.7 complex')
        ax.set_xlabel('Wavelength (microns)')
        ax.set_ylabel('Flux (W/m^2)')

        ensure_exists(output_dir)
        pdf_filename = output_dir + basename + '_aromatic_fit_1.pdf'
        print('Saved: ', pdf_filename)
        fig3.savefig(pdf_filename, format='pdf', bbox_inches='tight')

        plt.close()
        fig3.clear()
        # End comments if no 7.2

        if StrLine77[0] > 0:  # Use if spectrum has 7.2
            # Create limits of subtraction
            lim69_a = np.where(waveLim77 < x77[0])
            lim72_a = np.where((waveLim77 >= x77[0]) & (waveLim77 <= x77[1]))
            limOver_a = np.where(waveLim77 > x77[1])

            flux69_a = fluxLim77[lim69_a]
            flux72_a = (
                StrLine77[0] *
                waveLim77[lim72_a] +
                StrLine77[1])  # Straight Line
            fluxOver_a = fluxLim77[limOver_a]

            fluxFull77 = []
            fluxFull77 = np.append(flux69_a, flux72_a)
            # Create array from wavelength subtraction
            fluxFull77 = np.append(fluxFull77, fluxOver_a)

        else:
            fluxFull77 = fluxLim77  # Use if spectrum has no 7.2

        return waveLim77, fluxLim77, errLim77, fluxFull77

    def fit_gaussians(waveLim77, fluxLim77, errLim77, fluxFull77,
                      fnameStr='temp_label'):

        # End commented section
        # fluxFull77 = fluxLim77 # Comment if spectrum has a 7.2 feature

        # Define feature:
        feature = np.where(
            (waveLim77 > 7.1) & (
                waveLim77 < 8.9))  # Change as needed

        # # Fit Gaussian
        # fit77 = multigaussfit(
        #     waveLim77[feature], fluxFull77[feature], ngauss=4,
        #     err=errLim77[feature],
        #     params=[5e-15,7.5,0.07,5.8e-15,7.75,0.06,
        #             1.2e-15,8.1,0.07,1.8e-15,8.65,0.07],
        #     limitedmin=[True,True,True,True,True,True,
        #                 True,True,True,True,True,True],
        #     limitedmax=[False,True,False,False,True,False,
        #                 False,True,False,True,True,False],
        #     minpars=[0,7.4,0.05,0,7.7,0.2e-16,8.1,
        #              0.01,0.7e-16,8.4,0.01],
        #     maxpars=[3e-14,7.7,0.1,3e-14,8.1,0.2,
        #              3e-14,8.4,0.2,3e-14,8.7,0.2]
        # )

        fit77 = multigaussfit(
            waveLim77[feature],
            fluxFull77[feature],
            ngauss=4,
            err=errLim77[feature],
            params=[
                1.25e-14,
                7.68,
                0.1,
                0.2e-14,
                7.95,
                0.06,
                3e-15,
                8.227557,
                0.15,
                0.3e-14,
                8.609484,
                0.08],
            limitedmin=[
                True,
                True,
                True,
                True,
                True,
                True,
                True,
                True,
                True,
                True,
                True,
                True],
            minpars=[
                0.2e-18,
                7.5,
                0.05,
                0.2e-18,
                7.75,
                0.01,
                0.2e-18,
                8.1,
                0.03,
                0.3e-18,
                8.5,
                0],
            limitedmax=[
                True,
                True,
                True,
                True,
                True,
                True,
                False,
                True,
                True,
                True,
                True,
                False],
            maxpars=[
                2.3e-12,
                7.9,
                0.25,
                1.5e-12,
                8.2,
                0.25,
                8e-12,
                8.5,
                0.2,
                1e-11,
                8.7,
                0.12])

        # print('Fit parameters of the 7.7 micron complex:')
        # print(fit77[0])
        waveArr = np.arange(waveLim77[feature][0],
                            waveLim77[feature][-1], 0.0001)
        # Seperate Gaussian functions
        Gauss76 = fit77[0][0] * \
            np.exp(-(waveArr - fit77[0][1])**2 / (2 * fit77[0][2]**2))
        Gauss79 = fit77[0][3] * \
            np.exp(-(waveArr - fit77[0][4])**2 / (2 * fit77[0][5]**2))
        Gauss82 = fit77[0][6] * \
            np.exp(-(waveArr - fit77[0][7])**2 / (2 * fit77[0][8]**2))
        Gauss86 = fit77[0][9] * \
            np.exp(-(waveArr - fit77[0][10])**2 / (2 * fit77[0][11]**2))

        # err76 = compute_feature_uncertainty(
        #     fit77[0][1], fit77[0][2], waveLim77[feature], rms
        # )
        # err79 = compute_feature_uncertainty(
        #     fit77[0][4], fit77[0][5], waveLim77[feature], rms
        # )
        # err82 = compute_feature_uncertainty(
        #     fit77[0][7], fit77[0][8], waveLim77[feature], rms
        # )

        fluxFeat = Gauss76 + Gauss79 + Gauss82
        waveFeat = waveArr
        area77 = simps(fluxFeat, waveFeat)

        # fitErr77 = area77 * np.sqrt(
        #     (fit77[2][0]/fit77[0][0])**2 + (fit77[2][2]/fit77[0][2])**2 +
        #     (fit77[2][3]/fit77[0][3])**2 + (fit77[2][5]/fit77[0][5])**2 +
        #     (fit77[2][6]/fit77[0][6])**2 + (fit77[2][8]/fit77[0][8])**2
        # )
        # errTot77 = fitErr77

        # SNR77 = area77/errTot77

        # wave0 = 7.9  # Initial guess for central wavelength
        # errPercent = 1
        # count = 0
        '''
        # Define function to calculate difference in blue and red flux
        def areaEq(wave0):
            blue = np.where(waveFeat<=wave0)
            # Integration limits to find central wavelengths
            red = np.where(waveFeat>wave0)
            print '**************'
            area77b = simps(fluxFeat[blue], waveFeat[blue])
            area77r = simps(fluxFeat[red], waveFeat[red])
            return area77b - area77r

        lambdaC = fsolve(areaEq, wave0, xtol=1.5E-1)
        # Optimise difference in blue and red flux to find central wavelength
        print 'Central wavelength of 7.7 complex'
        print lambdaC

        while errPercent >= 0.01:
            count = count + 1
            #print count
            blue = np.where(waveFeat<=wave0)
            # Integration limits to find central wavelengths

            red = np.where(waveFeat>wave0)
            area77b = simps(fluxFeat[blue], waveFeat[blue])
            area77r = simps(fluxFeat[red], waveFeat[red])
            errPercent = np.absolute((area77b-area77r)/((area77b+area77r)/2))
            if area77b > area77r:
                wave0 = wave0 - 0.001
            elif area77r > area77b:
                wave0 = wave0 + 0.001
            else:
                continue
            #print area77b, area77r, wave0
            if count > 1000:
                break

        print 'error: ', errPercent
        print 'count: ', count
        lambdaC  = wave0
        print 'Central wavelength of 7.7 complex: ', lambdaC

        blue1 = np.where(waveFeat<=lambdaC)
        red1 = np.where(waveFeat>lambdaC)
        area77blue = simps(fluxFeat[blue1], waveFeat[blue1])
        area77red  = simps(fluxFeat[red1], waveFeat[red1])

        print 'Total integrated flux of 7.7 complex: ', area77
        #print 'SNR77: ', SNR77
        print 'Blue flux: ', area77blue
        print 'Red flux: ', area77red
        '''
        # Plot Gaussian fit
        fig4, ax = plt.subplots()  # Define figure

        ax.plot(waveLim77, fluxLim77, '-r', label=fnameStr, lw=2)
        ax.errorbar(
            waveLim77,
            fluxLim77,
            errLim77,
            color='r',
            ecolor='0.45',
            lw=2,
            elinewidth=1)
        ax.plot(waveLim77[feature], fit77[1], '-g', label='Spectral fit', lw=2)
        # ax.plot(waveLim2_a, StrLineFit77, 'b-', lw=2)
        # Plot straight line - comment out if no 7.2 feature

        # ax.plot(x77, y77, 'bo')
        # Straight line anchor points - comments out
        # if no 7.2 feature

        # Overplot individual Gaussian functions
        ax.plot(waveArr, Gauss76, '-g', lw=2)
        ax.fill_between(waveArr, Gauss76, facecolor='green', alpha=0.15)
        ax.plot(waveArr, Gauss79, '-g', lw=2)
        ax.fill_between(waveArr, Gauss79, facecolor='green', alpha=0.15)
        ax.plot(waveArr, Gauss82, '-g', lw=2)
        ax.fill_between(waveArr, Gauss82, facecolor='green', alpha=0.15)
        ax.plot(waveArr, Gauss86, '-g', lw=2)
        ax.fill_between(waveArr, Gauss86, facecolor='green', alpha=0.15)

        # ax.axvline(x=lambdaC, color='black', ls='-', lw=2)
        ax.axhline(y=0, color='b', ls='-', zorder=-10, lw=2)
        ax.legend(loc=0, fontsize='small')
        ax.grid()
        ax.minorticks_on()

        ax.set_title(fnameStr + ' 7.7 complex - fit')
        ax.set_xlabel(r'Wavelength ($\mu m$)')
        ax.set_ylabel('Flux ($W$/$m^2$)')
        axes = plt.gca()
        axes.set_xlim([7.1, 9])
        # axes.set_ylim([0,2e-15])

        ensure_exists(output_dir)
        pdf_filename = output_dir + basename + '_aromatic_fit_2.pdf'
        print('Saved: ', pdf_filename)
        fig4.savefig(pdf_filename, format='pdf', bbox_inches='tight')

        plt.close()
        fig4.clear()

        return fit77, area77, feature

    waveLim77, fluxLim77, errLim77, fluxFull77 = \
        fit_straight_line(wave, flux, fluxerr)

    fit77, area77, feature = fit_gaussians(
        waveLim77, fluxLim77, errLim77, fluxFull77)

    return waveLim77, fit77, area77, feature

def fit_all(basename, wave, flux, fluxerr, rms, output_dir):
    """Fit Gaussians and straight line at the same time or something. Or maybe
    no straight line."""
    def param_constraints_OK(p0, line, index):
        # Test if any parameter hitting min/max of constrained range.

        def nums_equal(num1, num2, acc=0.01):
            """Returns True if numbers are equal within some accuracy."""
            if np.abs(num1 - num2) < acc:
                return False
            else:
                return True

        # Line position.
        pindex = index * 3 + 1
        fixed_position = p0['fixed'][pindex]

        if not fixed_position:
            limited_min = p0['limitedmin'][pindex]
            limited_max = p0['limitedmax'][pindex]
            if limited_min:
                if not nums_equal(p0['minpars'][pindex], line['position']):
                    print('Hitting minimum line position.')
                    return False
            if limited_max:
                if not nums_equal(p0['maxpars'][pindex], line['position']):
                    print('Hitting maximum line position.')
                    return False

        # Line sigma.
        pindex = index * 3 + 2
        fixed_sigma = p0['fixed'][pindex]

        if not fixed_sigma:
            limited_min = p0['limitedmin'][pindex]
            limited_max = p0['limitedmax'][pindex]
            if limited_min:
                if not nums_equal(p0['minpars'][pindex], line['sigma']):
                    print('Hitting minimum line sigma.')
                    return False
            if limited_max:
                if not nums_equal(p0['maxpars'][pindex], line['sigma']):
                    print('Hitting maximum line sigma.')
                    return False

        return True

    def fit_4gauss_2lines(wave, flux, fluxerr, trim, trim_wide):

        # Multigauss fit. Intensity, center, sigma (or FWHM?).
        yscale = flux[trim]
        guess = np.nanmax(yscale)
        yfit = multigaussfit(
            wave[trim], flux[trim], ngauss=4, err=fluxerr[trim],
            params=[
                # 0.2e-14,  6.90, 0.10,
                # 0.2e-14,  7.23, 0.05,
                guess / 2.,  7.68, 0.10,
                guess,  7.95, 0.06,
                guess / 2.,  8.23, 0.15,
                guess / 2.,  8.61, 0.08
            ],
            limitedmin=[True] * 12,
            limitedmax=[True] * 12,
            minpars=[
                # 0, 6.8, 0.04,
                # 0, 7, 0,
                0, 7.5, 0.05,
                0, 7.75, 0.01,
                0, 8.1, 0.03,
                0, 8.5, 0
            ],
            maxpars=[
                # 0.5e-13, 7, 0.2,
                # 0.1e-13, 7.25, 0.2,
                guess, 7.9, 0.25,
                guess, 8.2, 0.25,
                guess, 8.5, 0.2,
                guess, 8.7, 0.12
            ])

        g76 = onedgaussian(wave, 0, yfit[0][0], yfit[0][1], yfit[0][2])
        g78 = onedgaussian(wave, 0, yfit[0][3], yfit[0][4], yfit[0][5])
        g82 = onedgaussian(wave, 0, yfit[0][6], yfit[0][7], yfit[0][8])
        g86 = onedgaussian(wave, 0, yfit[0][9], yfit[0][10], yfit[0][11])
        model = g76 + g78 + g82 + g86

        # Multigauss fit. Intensity, center, sigma (or FWHM?).
        resid = flux - model
        yfit2 = multigaussfit(
            wave, resid, ngauss=2,
            params=[
                np.nanmax(resid) / 2.,  6.88, 0.10,
                np.nanmax(resid) / 2.,  7.23, 0.05,
            ],
            limitedmin=[True] * 6,
            limitedmax=[True] * 6,
            minpars=[
                0, 6.8, 0.04,
                0, 7, 0,
            ],
            maxpars=[
                np.nanmax(resid), 7, 0.2,
                np.nanmax(resid), 7.30, 0.2,
            ])

        line69 = onedgaussian(wave, 0, yfit2[0][0], yfit2[0][1], yfit2[0][2])
        line72 = onedgaussian(wave, 0, yfit2[0][3], yfit2[0][4], yfit2[0][5])

        wpeak = {
            '69': yfit2[0][1],
            '72': yfit2[0][4],
        }

        return g76, g78, g82, g86, line69, line72, model, yfit, yfit2, wpeak

    def fit_6gauss(wave, flux, fluxerr, trim, basename):

        # Initial parameters and constraints.
        yscale = flux[trim]
        guess = np.nanmax(yscale)
        p_init = params_6gauss(basename, guess)

        # If fluxerr[trim] has zeroes, don't use errors for now?
        if 0 in fluxerr[trim]:
            errpass = None
        else:
            errpass = fluxerr[trim]

        if basename in ['J052043_CWsub', 'SMPSMC006_CWsub']:
            errpass = None

        # Multigauss fit. Intensity, center, sigma (or FWHM?).
        yfit = multigaussfit(
            wave[trim], flux[trim], ngauss=6, err=errpass,
            params=p_init['params'],
            limitedmin=p_init['limitedmin'],
            limitedmax=p_init['limitedmax'],
            fixed=p_init['fixed'],
            minpars=p_init['minpars'],
            maxpars=p_init['maxpars']
            )

        # Save results.
        features = ('line69', 'line72', 'g76', 'g78', 'g82', 'g86')
        keys = ('scale_factor', 'position', 'sigma')
        keys_err = ('scale_factor_err', 'position_err', 'sigma_err')
        results = {}

        for i in range(6):
            fit_params = (yfit[0][3*i: 3*i+3])
            fit_params_err = (yfit[2][3*i: 3*i+3])

            integrated_fluxerr = compute_feature_uncertainty(
                fit_params[1], fit_params[2], wave[trim], rms
            )
            results[features[i]] = dict(zip(keys, fit_params))
            results[features[i]].update(dict(zip(keys_err, fit_params_err)))
            results[features[i]]['wave'] = wave
            results[features[i]]['spectrum'] = onedgaussian(
                wave, 0, *fit_params)
            results[features[i]]['integrated_flux'] = simps(
                results[features[i]]['spectrum'], results[features[i]]['wave'])
            results[features[i]]['integrated_fluxerr'] = integrated_fluxerr

        return yfit, results, p_init

    print(basename)

    fit4 = False

    if fit4:

        # Only fit 7-9 micron zone.
        trim = np.where((wave > 7.3) & (wave < 9.2))
        trim_wide = np.where((wave >= 6.0) & (wave <= 10))

        # Return fit.
        g76, g78, g82, g86, line69, line72, model, yfit, yfit2, wpeak = \
            fit_4gauss_2lines(wave, flux, fluxerr, trim, trim_wide)

        flux69 = simps(line69, wave)
        flux72 = simps(line72, wave)

        # Plot results.
        fig = plt.figure()
        gs = gridspec.GridSpec(ncols=1, nrows=2, figure=fig, hspace=0.3)
        ax1 = plt.subplot(gs[0])
        ax2 = plt.subplot(gs[1])

        # Upper panel.
        gsum = g76 + g78 + g82
        ax1.plot(wave[trim_wide], flux[trim_wide], label='Data')
        ax1.plot(wave[trim_wide], model[trim_wide],
                 label='Model (Flux: {:.2e} W/m^2)'.format(simps(gsum, wave)))
        for index, item in enumerate((g76, g78, g82, g86)):
            ax1.fill_between(
                wave[trim_wide],
                wave[trim_wide] * 0,
                item[trim_wide],
                lw=0.5,
                alpha=0.3)
        ax1.axhline(y=0, ls='--', lw=0.5, color='k')
        ax1.legend(loc=0)
        xmin, xmax = ax1.get_xlim()

        # Lower panel.
        trim_wide2 = np.where((wave >= 6.5) & (wave <= 10))
        ax2.plot(
            wave[trim_wide2],
            flux[trim_wide2] -
            model[trim_wide2],
            label='Residual from 4gauss')
        label69 = \
            '6.9 ({:.2f} µm, Flux: {:.2e} W/m^2)'.format(wpeak['69'], flux69)
        label72 = \
            '7.2 ({:.2f} µm, Flux: {:.2e} W/m^2)'.format(wpeak['72'], flux72)
        ax2.fill_between(wave, wave * 0, line69, alpha=0.3, label=label69)
        ax2.fill_between(wave, wave * 0, line72, alpha=0.3, label=label72)
        ax2.axhline(y=0, ls='--', lw=0.5, color='k')
        ax2.legend(loc=0)
        ax2.set_xlim(xmin, xmax)

        # Save.
        savename = output_dir + 'fullspec/' + basename + '_test.pdf'
        fig.savefig(savename, bbox_inches='tight')
        print('Saved: ', savename)
        plt.close()
        fig.clear()

    else:

        # Only fit 7-9 micron zone.
        trim = np.where((wave > 6.0) & (wave < 10))

        # Try 6-components.
        yfit, results, p0 = fit_6gauss(wave, flux, fluxerr, trim, basename)

        # # Try 6, with LMFIT!
        # yfit2, results2, p02 = fit_6gauss_lmfit(wave, flux, fluxerr, trim)
        # st()

        # Plot results.
        fig = plt.figure(figsize=(8, 6))
        gs = gridspec.GridSpec(ncols=1, nrows=2, figure=fig, hspace=0.3)
        ax1 = plt.subplot(gs[0])
        ax2 = plt.subplot(gs[1])

        ##############################
        # Upper panel.
        ##############################

        flux77 = sum([results[x]['integrated_flux']
                      for x in ('g76', 'g78', 'g82')])
        flux77_err = sum([results[x]['integrated_fluxerr']
                          for x in ('g76', 'g78', 'g82')])
        spec77 = results['g76']['spectrum'] + results['g78']['spectrum'] + \
            results['g82']['spectrum']

        centroid77 = np.sum(spec77 * wave) / np.sum(spec77)
        model_label = \
            r'Model (g1-3: {:.2f} µm, {:.2e} +- ' \
            '{:.2e} W/m$^2$)'.format(centroid77, flux77, flux77_err)
        ax1.errorbar(wave[trim], flux[trim], yerr=fluxerr[trim], label='Data')
        # ax1.plot(wave[trim], flux[trim], label='Data')

        ax1.plot(wave[trim], yfit[1], label=model_label, zorder=1000)
        for index, key in enumerate(results):
            ax1.fill_between(wave[trim], wave[trim] * 0,
                             results[key]['spectrum'][trim],
                             lw=0.5, alpha=0.3)
        ax1.axvline(x=centroid77, color='k', ls='-', lw=0.5)
        ax1.axhline(y=0, ls='--', lw=0.5, color='k')
        ax1.axvline(x=6.9, color='k', ls='-', lw=0.5)
        ax1.axvline(x=7.25, color='k', ls='-', lw=0.5)
        ax1.legend(loc=0, fontsize=8)
        xmin, xmax = ax1.get_xlim()

        ##############################
        # Lower panel.
        ##############################

        f72_69 = results['line72']['integrated_flux'] / \
            results['line69']['integrated_flux']

        label = 'Residuals (7.2/6.9 = {}' \
                ')'.format(quant_str(f72_69, precision="0.01"))
        ax2.plot(wave[trim], flux[trim] - yfit[1],
                 label=label)
        ax2.axvline(x=6.9, color='k', ls='-', lw=0.5)
        ax2.axvline(x=7.25, color='k', ls='-', lw=0.5)

        param_OK_list = [True]
        for index, key in enumerate(results):
            line = results[key]
            label = '{:.2f} µm, {:.2e} +- {:.2e} W/m^2, FWHM={:.2f} µm'.format(
                line['position'], line['integrated_flux'],
                line['integrated_fluxerr'],
                to_fwhm(line['sigma'])
            )
            param_OK_list.append(param_constraints_OK(p0, line, index))
            ax2.fill_between(wave[trim], wave[trim] * 0,
                             results[key]['spectrum'][trim],
                             lw=0.5, alpha=0.3, label=label)
        ax2.axhline(y=0, ls='--', lw=0.5, color='k')
        mylegend = ax2.legend(loc=0, fontsize=8)

        for index, text in enumerate(mylegend.get_texts()):
            if not param_OK_list[index]:
                text.set_color("red")

        # Save.
        savename = output_dir + 'fullspec/' + basename + '_6gauss.pdf'
        fig.savefig(savename, bbox_inches='tight')
        print('Saved: ', savename)
        plt.close()
        fig.clear()

        # Insert the 7.7 results.
        results['pah77'] = {
            'flux': flux77,
            'fluxerr': flux77_err,
            'centroid': centroid77,
        }

        pkl_name = output_dir + 'numeric/' + basename + '.pkl'
        # Record results to disk.
        with open(pkl_name, 'wb') as file:
            pickle.dump(results, file, protocol=pickle.HIGHEST_PROTOCOL)
        print('Saved: ', pkl_name)

        txt_name = output_dir + 'numeric/' + basename + '.txt'
        with open(txt_name, 'w') as f:
            f.write('Object name, flux (W/m^2), flux error (W/m^2)\n')
            f.write(basename + '\n')
            f.write(str(flux77) + '\n')
            f.write(str(flux77_err) + '\n')
        print('Saved: ', txt_name)

    return (basename, flux77, flux77_err)

def measure_112(basename, wave, flux, fluxerr, rms, output_dir,
                fnameStr='temp_label'):

    lim11 = np.where((wave >= 10.8) & (wave <= 12))
    waveLim11 = wave[lim11]
    fluxLim11 = flux[lim11]
    errLim11 = fluxerr[lim11]

    feature11 = np.where((waveLim11 >= 11.1) & (
        waveLim11 <= 11.85))  # Define actual feature
    # Start at 11.1, change end point as needed

    area11 = simps(fluxLim11[feature11], waveLim11[feature11])  # Integrate

    # print('Integrated flux of 11.2 feature: ', area11)

    fig5, ax = plt.subplots()  # Define figure

    ax.plot(waveLim11, fluxLim11, '-r', label=fnameStr, lw=2)
    ax.errorbar(
        waveLim11,
        fluxLim11,
        errLim11,
        color='r',
        ecolor='0.45',
        lw=2,
        elinewidth=1)
    ax.fill_between(
        waveLim11[feature11],
        fluxLim11[feature11],
        facecolor='red',
        alpha=0.15)
    ax.axhline(y=0, color='k', ls='-', zorder=-10, lw=2)

    ax.legend(loc=0, fontsize='small')
    ax.set_title(fnameStr + ' -- 11.2 feature')
    ax.set_xlabel('Wavelength (microns)')
    ax.set_ylabel('Flux (W/m^2)')
    ax.grid()
    ax.minorticks_on()

    ensure_exists(output_dir)
    pdf_filename = output_dir + basename + '_11.pdf'
    print('Saved: ', pdf_filename)
    fig5.savefig(pdf_filename, format='pdf', bbox_inches='tight')

    plt.close()
    fig5.clear()

    return area11

def save_fit_parameters(output_dir, results):

    ensure_exists(output_dir)

    fitAli, fit77, basename, waveLim, waveLim77, area69, \
        area72, area77, area11, SNR69, SNR72, feature = results

    fnameStr = basename

    # Save all fit parameters in one file
    arrParamsID = np.append(fitAli[0] * 0, fit77[0] * 0 + 1)
    arrParams = np.append(fitAli[0], fit77[0])
    # arrParamsErr = np.append(fitAli[2], fit77[2])

    txt_filename = output_dir + fnameStr + '_fitParams.txt'
    print('Saved: ', txt_filename)
    np.savetxt(
        txt_filename,
        np.c_[
            arrParamsID,
            arrParams],
        delimiter=',',
        header='Gaussian fit parameters\n col1: ID - 0=aliphatic, '
               '1=7.7 complex, col2: parameters, col3: fit error')
    # np.savetxt(fnameStr + '_fitParams.txt',
    # np.c_[arrParamsID, arrParams, arrParamsErr], delimiter=',',
    # header='Gaussian fit parameters\n col1: ID - 0=aliphatic, 1=7.7 complex,
    # col2: parameters, col3: fit error')

    # Save all fit models in one file
    arrID = np.append(fitAli[1] * 0, fit77[1] * 0 + 1)
    arrWave = np.append(waveLim, waveLim77[feature])
    arrFluxDensity = np.append(fitAli[1], fit77[1])

    txt_filename = output_dir + fnameStr + '_fitModel.txt'
    print('Saved: ', txt_filename)
    np.savetxt(
        txt_filename,
        np.c_[
            arrID,
            arrWave,
            arrFluxDensity],
        delimiter=',',
        header='Full model fit\n col1: ID - 0=aliphatic, 1=7.7 complex, '
               'col2: wavelength, col3: flux density')

    # arrIntegratedFluxes = np.array([area69, area72, area77, area77blue,
    # area77red, area11, lambdaC]) # Save all integrated fluxes in one file
    # Save all integrated fluxes in one file
    # arrIntegratedFluxes = np.array([area69, area72, area77, area11])

    txt_filename = output_dir + fnameStr + '_intFlux.txt'
    print('Saved: ', txt_filename)
    np.savetxt(
        txt_filename,
        np.c_[
            area69,
            area72,
            area77,
            area11],
        delimiter=',',
        header='Integrated fluxes of features\n col1: 6.9 microns, '
               'col2: 7.2 microns, col3: total 7.7 complex, col4: blue 7.7, '
               'col5: red 7.7, col6: 11.2 feature, '
               'col7: central wavelength of 7.7 (microns)')

    txt_filename = output_dir + fnameStr + '_SNR.txt'
    print('Saved: ', txt_filename)
    np.savetxt(
        txt_filename,
        np.c_[
            SNR69,
            SNR72],
        delimiter=',',
        header='col1:SNR69, col2: SNR72, col3: SNR77')

    txt_filename = output_dir + fnameStr + 'Full.txt'
    workFile = open(txt_filename, 'w')  # Write all data into single file
    workFile.write(fnameStr + 'Fitting and integrated flux data\n\n')

    workFile.write('Section 1: Aliphatic features\n')
    workFile.write(
        'Gaussian fitting parameters for 6.9 and 7.2 micron features\n')
    workFile.write(str(fitAli[0]) + '\n')
    workFile.write('Errors on aliphatic fitting parameters\n')
    workFile.write(str(fitAli[2]) + '\n')
    workFile.write('Fit model - aliphatic features\n')
    workFile.write(str(waveLim) + '\n' + str(fitAli[1]) + '\n\n')
    workFile.write('Integrated fluxes of aliphatic features\n')
    workFile.write('6.9 micron feature ' + str(area69) + '\n')
    workFile.write('7.2 micron feature ' + str(area72) + '\n')
    workFile.write('S/N of 6.9: ' + str(SNR69) + '\n')
    workFile.write('S/N of 7.2: ' + str(SNR72) + '\n\n')

    workFile.write('Section 2: 7.7 micron complex\n')
    workFile.write('Gaussian fitting parameters for 7.7 micron complex\n')
    workFile.write(str(fit77[0]) + '\n')
    workFile.write('Errors on fitting parameters\n')
    workFile.write(str(fit77[2]) + '\n')
    workFile.write('Fit model - 7.7 micron complex\n')
    workFile.write(str(waveLim77[feature]) + '\n' + str(fit77[1]) + '\n')
    workFile.write('Integrated fluxes\n')
    workFile.write('Total integrated flux of complex: ' + str(area77) + '\n')
    # workFile.write('Blue flux: ' + str(area77blue) + '\n')
    # workFile.write('Red flux: ' + str(area77red) + '\n')
    # workFile.write('SNR77: ' + str(SNR77) + '\n')
    # workFile.write('Central wavelength of 7.7 complex:' + str(lambdaC) +
    # '\n\n')

    workFile.write('Section 3: 11.2 micron feature\n')
    workFile.write('Integrated flux of 11.2 micron feature:\n')
    workFile.write(str(area11) + '\n')

    workFile.close()
    print('Saved: ', txt_filename)

    return


# Perform imports here:
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
import pandas as pd

# Launch the application:
app = dash.Dash()

# Create a DataFrame from the .csv file:
df = pd.read_csv('OldFaithful.csv')

# Create a Dash layout that contains a Graph component:
app.layout = html.Div([
    dcc.Graph(
        id='old_faithful',
        figure={
            'data': [
                go.Scatter(
                    x = df['X'],
                    y = df['Y'],
                    mode = 'markers'
                )
            ],
            'layout': go.Layout(
                title = 'Old Faithful Eruption Intervals v Durations',
                xaxis = {'title': 'Duration of eruption (minutes)'},
                yaxis = {'title': 'Interval to next eruption (minutes)'},
                hovermode='closest'
            )
        }
    )
])

# Add the server clause:
if __name__ == '__main__':
    app.run_server()


from typing import List, Tuple

import numpy as np

from mathy_envs.envs.poly_simplify import PolySimplify
from mathy_envs.state import MathyEnvState, MathyObservation

def test_state_to_observation():
    """to_observation has defaults to allow calling with no arguments"""
    env_state = MathyEnvState(problem="4x+2")
    assert env_state.to_observation() is not None

def test_state_to_observation_normalization():
    """normalize argument converts all values to range 0.0-1.0"""
    env_state = MathyEnvState(problem="4+2")
    obs: MathyObservation = env_state.to_observation(normalize=False)
    assert np.max(obs.values) == 4.0

    norm: MathyObservation = env_state.to_observation(normalize=True)
    assert np.max(norm.values) == 1.0

def test_state_to_observation_normalized_problem_type():
    """normalize argument converts all values and type hash to range 0.0-1.0"""
    env_state = MathyEnvState(problem="4+2")
    obs: MathyObservation = env_state.to_observation()
    print(obs.type)
    assert np.max(obs.time) <= 1.0
    assert np.min(obs.time) >= 0.0

    assert np.max(obs.values) <= 1.0
    assert np.min(obs.values) >= 0.0

    assert np.max(obs.type) <= 1.0
    assert np.min(obs.type) >= 0.0

def test_state_encodes_hierarchy():
    """Verify that the observation generated encodes hierarchy properly
    so the model can determine the precise nodes to act on"""

    diff_pairs: List[Tuple[str, str]] = [
        ("4x + (3u + 7x + 3u) + 4u", "4x + 3u + 7x + 3u + 4u"),
        ("7c * 5", "7 * (c * 5)"),
        ("5v + 20b + (10v + 7b)", "5v + 20b + 10v + 7b"),
        ("5s + 60 + 12s + s^2", "5s + 60 + (12s + s^2)"),
    ]
    env = PolySimplify()

    for one, two in diff_pairs:
        state_one = MathyEnvState(problem=one)
        obs_one = state_one.to_observation(env.get_valid_moves(state_one))

        state_two = MathyEnvState(problem=two)
        obs_two = state_two.to_observation(env.get_valid_moves(state_two))

        assert obs_one.nodes != obs_two.nodes

def test_state_sanity():
    state = MathyEnvState(problem="4+4")
    assert state is not None

def test_state_encode_player():
    env_state = MathyEnvState(problem="4x+2")
    env_state = env_state.get_out_state(
        problem="2+4x", moves_remaining=10, action=(0, 0)
    )
    agent = env_state.agent
    assert agent.problem == "2+4x"
    assert agent.moves_remaining == 10
    assert agent.action == (0, 0)

def test_state_serialize_string():
    env_state = MathyEnvState(problem="4x+2")
    for i in range(10):
        env_state = env_state.get_out_state(
            problem="2+4x", moves_remaining=10 - i, action=(i, i)
        )

    state_str = env_state.to_string()
    compare = MathyEnvState.from_string(state_str)
    assert env_state.agent.problem == compare.agent.problem
    assert env_state.agent.moves_remaining == compare.agent.moves_remaining
    for one, two in zip(env_state.agent.history, compare.agent.history):
        assert one.raw == two.raw
        assert one.action == two.action

def test_state_serialize_numpy():
    env_state = MathyEnvState(problem="4x+2")
    for i in range(10):
        env_state = env_state.get_out_state(
            problem="2+4x", moves_remaining=10 - i, action=(i, i)
        )

    state_np = env_state.to_np()
    compare = MathyEnvState.from_np(state_np)
    assert env_state.agent.problem == compare.agent.problem
    assert env_state.agent.moves_remaining == compare.agent.moves_remaining
    for one, two in zip(env_state.agent.history, compare.agent.history):
        assert one.raw == two.raw
        assert one.action == two.action


import cv2
import numpy as np
import wavio
import time
import random
import os
import math

# Constants
RATE = 44100  # Sample rate (samples/second)
AMPLITUDE = 1  # Constant amplitude
FADE_DURATION = 0.1  # 100ms fade in and fade out

sp = os.path.sep
audio_data = []
brightnesses = []

# C4 Major Scale Frequencies
C4_MAJOR_SCALE = [49.7, 66.3, 83.5, 88.5, 111.5, 118.1, 125.1, 140.3, 140.5, 148.8, 157.7, 167.0, 187.5, 198.6, 210.5, 223.0, 250.3, 265.2, 280.9, 297.6, 315.3, 334.1, 354.0, 375.0, 420.9, 446.0, 472.5, 500.6, 561.9]
C4_MAJOR_SCALE = sorted(C4_MAJOR_SCALE)
previous_sounds = []
def create_video(out):
    pass
def process_lines(result, line, frame):
    sz = len(line)
    average_r = 0
    average_g = 0
    average_b = 0
    for el in line:
        x, y = el
        #print(frame[y, x])
        R, G, B = frame[y, x]
        average_r += R
        average_g += G
        average_b += B
    average_r //= sz
    average_g //= sz
    average_b //= sz
    result[0] += average_r
    result[1] += average_g
    result[2] += average_b

    return result
def convert_to_sound(pixels_to_convert):

    red, green, blue = pixels_to_convert  # get pixel RGB value
    luminosity = (red + green + blue) // 3 # calculate brightness

    # Calculate the index of the note to play based on the luminosity value
    index = ((luminosity + 50) % 256) // 9

    # Check if the previous three notes played were the same, and if so, increment the index
    if len(previous_sounds) > 2:
        if previous_sounds[-1] == index and previous_sounds[-2] == index and previous_sounds[-3] == index:
            index += 1
            index %= len(C4_MAJOR_SCALE)

    # Add the index to the list of previous notes played
    previous_sounds.append(index)

    # Calculate the frequency of the note to play
    freq = C4_MAJOR_SCALE[index]

    # Calculate a duration so that the waveform ends near a zero crossing
    samples_per_wave = RATE / freq
    total_samples = int(samples_per_wave * np.round(RATE * 1 / samples_per_wave))

    # Generate a sine wave with the calculated frequency and duration
    t = np.linspace(0, total_samples / RATE, total_samples, endpoint=False)
    signal = np.sin(2 * np.pi * freq * t)

    # Apply a fade-in and fade-out to the waveform
    fade_in = np.linspace(0, 1, int(FADE_DURATION * RATE))
    fade_out = np.linspace(1, 0, int(FADE_DURATION * RATE))
    fade = np.ones_like(signal)
    fade[:len(fade_in)] = fade_in
    fade[-len(fade_out):] = fade_out
    signal *= fade

    # Apply the desired amplitude to the waveform
    signal *= AMPLITUDE

    # Add the waveform to the list of audio data
    audio_data.append(signal)

# Define a function to generate a list of pixels along a line between two points using Bresenham's line algorithm
def bresenham_line(x0, y0, x1, y1):
        result = []
        for i in range(x0, x1 + 1):
            result.append((i, y0))
            if y0 != y1:
                result.append((i, y1))
        for i in range(y0 + 1, y1):
            result.append((x0, i))
            if x0 != x1:
                result.append((x1, i))
        return result

# Define a function to resolve a video file into audio and visual components
def resolve_video(video_path):
        # Open the video file
        cap = cv2.VideoCapture(video_path)
        fps = int(cap.get(cv2.CAP_PROP_FPS))
        frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

        # Get the dimensions of the video frames
        frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

        # Create an empty numpy ndarray to hold the video frames
        video_array = np.empty((frame_count, frame_height, frame_width, 3), dtype=np.uint8)

        # Loop through each frame in the video and add it to the numpy ndarray
        for i in range(frame_count):
            ret, frame = cap.read()
            if ret:
                video_array[i] = frame
            else:
                break

        # Release the video capture object
        cap.release()

        # Assuming that you have already loaded the video_array ndarray
        num_frames, frame_height, frame_width, _ = video_array.shape

        # Calculate the center point of the ndarray
        center_x = frame_width // 2
        center_y = frame_height // 2
        center_point = (center_x, center_y)

        # Define the color of the lines to draw
        R_colour = 0
        G_colour = 255
        B_colour = 0

        # Define some variables for processing the video frames
        coord = 0
        sz = frame_height + frame_width
        c = sz // frame_count + 1

        average_colour_for_second = [0, 0, 0]
        number_of_used_lines = 0
        a = 0
        gcd_height_width = math.gcd(frame_height, frame_width)
        deltax = frame_width // gcd_height_width
        deltay = frame_height // gcd_height_width

        # Loop through each frame in the video array and draw lines on the frame
        sp = os.path.sep
        fourcc = cv2.VideoWriter_fourcc(*'MP4V')
        out = cv2.VideoWriter(f'videos{sp}output.mp4', fourcc, 30.0, (frame_width, frame_height))
        for frame in video_array:
            number_of_used_lines += 1
            average_colour_for_frame = [0, 0, 0]
            for i in range(c):
                # Draw a line from the center point to each corner pixel of the frame
                x0 = coord * deltax
                y0 = coord * deltay
                x1 = frame_width-coord * deltax -1
                y1 = frame_height - coord * deltay - 1
                process_lines(average_colour_for_frame, bresenham_line(x0, y0, x1, y1), frame)
                cv2.rectangle(frame, (x0, y0), (x1, y1), (R_colour, G_colour, B_colour), 1)

            # Update the average color for the second
            average_colour_for_second[0] += average_colour_for_frame[0] // c
            average_colour_for_second[1] += average_colour_for_frame[1] // c
            average_colour_for_second[2] += average_colour_for_frame[2] // c

            # Update the coordinate for the next set of lines to draw
            coord += 1
            coord %= (frame_height // 2 // deltay)

            # If a second has passed, convert the average color to a sound and reset the average color
            if number_of_used_lines % fps == 0:
                average_colour_for_second[0] //= fps
                average_colour_for_second[1] //= fps
                average_colour_for_second[2] //= fps
                convert_to_sound(average_colour_for_second)
                average_colour_for_second = [0, 0, 0]
                a += 1

            # Write the frame to the output video file
            out.write(frame)

        # Release the VideoWriter object
        out.release()

        # Concatenate all the small signals into one signal
        global audio_data
        audio_data = np.hstack(audio_data)

        # Normalize the audio data to 16-bit PCM
        audio_data = (audio_data * (2 ** 15 - 1)).astype(np.int16)

        # Save the audio data to a .wav file
        wavio.write(f"sounds{sp}out.wav", audio_data, RATE)

# Define a function to run the video resolution process
def run(video_path):
    resolve_video(video_path)


import aiohttp

class ConsensusMixin:

    def valid_chain(self, chain):
        """检验链的有效性.
        Determine if a given blockchain is valid
        :param chain: <list> A blockchain
        :return: <bool> True if valid, False if not
        """

        last_block = chain[0]
        current_index = 1

        while current_index < len(chain):
            block = chain[current_index]
            print(f'{last_block}')
            print(f'{block}')
            print("\n-----------\n")
            # Check that the hash of the block is correct
            if block.previous_hash != last_block.hash:
                return False

            # Check that the Proof of Work is correct
            if not self.valid_proof(last_block['proof'], block['proof']):
                return False

            last_block = block
            current_index += 1

        return True

    async def resolve_conflicts(self):
        """
        共识算法解决冲突
        使用网络中最长的链.
        :return: <bool> True 如果链被取代, 否则为False
        """

        neighbours = self.nodes
        new_chain = None

        # We're only looking for chains longer than ours
        max_length = len(self.chain)

        # Grab and verify the chains from all the nodes in our network
        for node in neighbours:
            async with aiohttp.ClientSession() as session:
                async with session.get(f'https://{node}/chain') as resp:
                    if resp.status == 200:
                        resp_json = await resp.json()
                        length = resp_json['length']
                        chain = resp_json['chain']
                        # Check if the length is longer and the chain is valid
                        if length > max_length and self.valid_chain(chain):
                            max_length = length
                            new_chain = chain

        # Replace our chain if we discovered a new, valid chain longer than ours
        if new_chain:
            self.chain = new_chain
            return True

        return False


"""OpenAPI core contrib falcon handlers module"""
from json import dumps

from falcon import status_codes
from falcon.constants import MEDIA_JSON

from openapi_core.exceptions import MissingRequiredParameter
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.paths.exceptions import OperationNotFound
from openapi_core.templating.paths.exceptions import PathNotFound
from openapi_core.templating.paths.exceptions import ServerNotFound
from openapi_core.validation.exceptions import InvalidSecurity

class FalconOpenAPIErrorsHandler:

    OPENAPI_ERROR_STATUS = {
        MissingRequiredParameter: 400,
        ServerNotFound: 400,
        InvalidSecurity: 403,
        OperationNotFound: 405,
        PathNotFound: 404,
        MediaTypeNotFound: 415,
    }

    @classmethod
    def handle(cls, req, resp, errors):
        data_errors = [cls.format_openapi_error(err) for err in errors]
        data = {
            "errors": data_errors,
        }
        data_str = dumps(data)
        data_error_max = max(data_errors, key=cls.get_error_status)
        resp.content_type = MEDIA_JSON
        resp.status = getattr(
            status_codes,
            f"HTTP_{data_error_max['status']}",
            status_codes.HTTP_400,
        )
        resp.text = data_str
        resp.complete = True

    @classmethod
    def format_openapi_error(cls, error):
        return {
            "title": str(error),
            "status": cls.OPENAPI_ERROR_STATUS.get(error.__class__, 400),
            "class": str(type(error)),
        }

    @classmethod
    def get_error_status(cls, error):
        return error["status"]


import math
from regionofsupport import RegionOfSupport
from django.contrib.gis.geos import Point

class FeaturePoint:
    """
    Class based on the implementation of FeaturePoint2D.java by Luı́s Carlos in his thesis "Morphing techniques in
    spatio-temporal database" ("Aplicação de técnicas de morphing em bases de dados espacio-temporais")
    """

    def __init__(self, point, angle=0):
        self.point = point                  # geos point
        self.angle = angle
        self.prev, self.next = None, None   # geos points
        self.convex = None
        self.ft_size, self.ft_size_l, self.ft_size_r = 0, 0, 0
        self.ft_variation, self.ft_variation_l, self.ft_variation_r = 0, 0, 0
        self.side_ft_variation = 0
        self.n_val, self.t_val = 0, 0
        self.n_vector, self.t_vector = None, None

    def calculate_angle(self, p_minus, p_plus):
        a = self.point.distance(p_plus)
        b = self.point.distance(p_minus)
        c = p_minus.distance(p_plus)
        if a == 0 or b == 0 or c == 0:
            return 0

        # calculate the angle on (ab) and convert it to degrees
        angle = ((a * a) + (b * b) - (c * c)) / (2 * a * b)
        if angle <= -1:
            angle = -1
        elif angle >= 1:
            angle = 1
        return math.degrees(math.acos(angle))

    def set_angle(self, angle, _prev, _next):
        self.angle = angle
        self.prev = _prev
        self.next = _next

        # check convexness
        bx, by = self.point[0] - _prev[0], self.point[1] - _prev[1]
        cx, cy = _next[0] - _prev[0], _next[1] - _prev[1]
        convexness = bx*cy - by*cx
        if convexness >= 0:
            self.convex = True
        else:
            self.convex = False

    def get_least_opening_triangle_points(self):
        return [self.prev, self, self.next]

    def calculate_characteristics(self, fp_neighbours, perimeter):
        # create the ros
        ros = RegionOfSupport(fp_neighbours, self)

        # set fp properties
        self.n_val, self.n_vector = ros.ev_N, ros.e_N
        self.t_val, self.t_vector = ros.ev_T, ros.e_T

        # calculate the feature variation
        self.ft_variation = self.n_val / (self.n_val + self.t_val)
        if not self.convex:
            self.ft_variation *= -1

        # calculate the feature side variation
        fp_id = fp_neighbours.index(self)
        rol = RegionOfSupport(fp_neighbours[0: fp_id+2], self)
        ror = RegionOfSupport(fp_neighbours[fp_id-1: len(fp_neighbours)], self)
        self.ft_variation_l = rol.ev_N / (rol.ev_N + rol.ev_T)
        self.ft_variation_r = ror.ev_N / (ror.ev_N + ror.ev_T)
        if not self.convex:
            self.ft_variation_l *= -1
            self.ft_variation_r *= -1
        self.side_ft_variation = (self.ft_variation_l + self.ft_variation_r) / 2

        # calculate the side variation
        self.ft_size_r, self.ft_size_l = 0, 0
        _prev = fp_neighbours[0]
        for i in range(1, len(fp_neighbours)):
            if i == fp_id:
                _prev = self
                continue
            p = fp_neighbours[i]
            if isinstance(p, tuple):
                p = Point(p)
            else:
                p = p.point

            if not isinstance(_prev, tuple):
                _prev = _prev.point
            if i < fp_id:
                self.ft_size_l += p.distance(Point(_prev[0], _prev[1]))
            else:
                self.ft_size_r += p.distance(Point(_prev[0], _prev[1]))
            _prev = (p[0], p[1])

        self.ft_size_l /= perimeter
        self.ft_size_r /= perimeter
        self.ft_size = (self.ft_size_l + self.ft_size_r) / 2

    def calculate_similarity(self, fp, ft_variation, ft_side, ft_size):
        if self.ft_size > fp.ft_size:
            _max = self.ft_size
        else:
            _max = fp.ft_size
        cost = ft_variation * abs(self.ft_variation - fp.ft_variation)
        cost += ft_side * (abs(self.ft_variation_l-fp.ft_variation_r)+(abs(self.ft_variation_l-fp.ft_variation_l))) / 2
        cost += ft_size * (abs(self.ft_size_l - fp.ft_size_l)+abs(self.ft_size_r - fp.ft_size_r)) / 2
        return _max * cost

    def calculate_discard_cost(self, ft_variation, ft_side, ft_size):
        cost = ft_variation*abs(self.ft_variation) + ft_side*abs(self.side_ft_variation) + ft_size*abs(self.ft_size)
        return cost * self.ft_size

    def __str__(self):
        return f"({self.point.x},{self.point.y}) "


"""Websockets outbound transport."""

import logging

from aiohttp import ClientSession

from ...messaging.outbound_message import OutboundMessage

from .base import BaseOutboundTransport

class WsTransport(BaseOutboundTransport):
    """Websockets outbound transport class."""

    schemes = ("ws", "wss")

    def __init__(self) -> None:
        """Initialize an `WsTransport` instance."""
        super(WsTransport, self).__init__()
        self.logger = logging.getLogger(__name__)

    async def start(self):
        """Start the outbound transport."""
        self.client_session = ClientSession()
        return self

    async def stop(self):
        """Stop the outbound transport."""
        await self.client_session.close()
        self.client_session = None

    async def handle_message(self, message: OutboundMessage):
        """
        Handle message from queue.

        Args:
            message: `OutboundMessage` to send over transport implementation
        """
        # aiohttp should automatically handle websocket sessions
        async with self.client_session.ws_connect(message.endpoint) as ws:
            if isinstance(message.payload, bytes):
                await ws.send_bytes(message.payload)
            else:
                await ws.send_str(message.payload)


import numpy as np
import matplotlib.pyplot as plt
import math
from matplotlib import patches
plt.rcParams["font.sans-serif"]=["SimHei"]
plt.rcParams["axes.unicode_minus"]=False

g_edge_color = ["#C4323F", "#EF4143", "#033250", "#73BAD6", "#8AB07D", "#FB8402", "#56045A"]
g_color = ["none", "#EF4143", "none", "#73BAD6", "#8AB07D", "#FB8402", "#56045A"]
g_marker = ["^", "o", "s", "v", "o", "*", "8"]
legend=["簇1", "簇2", "簇3", "簇4", "簇5", "簇6", "簇7"]

config_dic = {"title": "K-means Result",
              "xlabel": "x-label",
              "ylabel": "y-label",
              "featrue_id": [0,1]}

def read_legend():
    with open("config.txt", 'r', encoding='UTF-8') as f:
        lines = f.readlines()
        for index, line in enumerate(lines):
            line = line.split(':')
            if len(line) != 2:
                continue
            if str(line[0]) == "featrue_id":
                line[1] = line[1].strip()
                ids = line[1].split(',')
                config_dic[str(line[0])][0] = int(ids[0])
                config_dic[str(line[0])][1] = int(ids[1])
            elif str(line[0]) in config_dic:
                line[1] = line[1].strip()
                config_dic[str(line[0])] = str(line[1])

if __name__ == '__main__':
    read_legend()
    data = np.loadtxt("data.csv", delimiter=",", dtype=np.float64)
    label = np.loadtxt("label.csv", delimiter=",", dtype=np.int32)

    label_num = label.max()

    for i in range(label_num):
        cur_label = i + 1
        mask = (label == cur_label)
        cur_data = data[mask]
        data_num = cur_data.shape[0]
        plt.scatter(cur_data[:,config_dic["featrue_id"][0]],
                    cur_data[:,config_dic["featrue_id"][1]],
                    marker=g_marker[i],
                    color=g_color[i],
                    edgecolors=g_edge_color[i],
                    label=legend[i])

    plt.legend(loc="lower left", fontsize='x-large')
    plt.title(config_dic["title"], fontsize='xx-large', verticalalignment='bottom')
    plt.xlabel(config_dic["xlabel"], fontsize='x-large')
    plt.ylabel(config_dic["ylabel"], fontsize='x-large')
    plt.show()


import base58
import base64
import sha3
from typing import Iterable, Iterator, Callable
from klefki.algebra.isomorphism import bijection
from klefki.curves.secp256k1 import (
    FiniteFieldCyclicSecp256k1 as CF,
)
import math
import hashlib
from hashlib import sha256
from functools import reduce

b58encode = bijection(base58.b58decode)(base58.b58encode)
b64encode = bijection(base64.b64decode)(base64.b64encode)

def int_to_byte(key: int, length=32) -> bytes:
    return key.to_bytes(length, byteorder='big')

def int_to_byte64(key: int) -> bytes:
    return int_to_byte(key, 64)

@bijection(int_to_byte)
def byte_to_int(byte: bytes) -> CF:
    return int.from_bytes(byte, "big")

def to_keccak256int(a: str) -> int:
    h = sha3.keccak_256()
    h.update(str.encode())
    return int.from_bytes(h.digest(), "big")

def dhash256(x: int) -> int:
    return sha256(sha256(x).digest()).digest()

def ripemd160(x) -> int:
    ripemd160 = hashlib.new('ripemd160')
    if isinstance(x, str):
        x = x.encode()
    if isinstance(x, list):
        for i in x:
            ripemd160.update(i)
    else:
        ripemd160.update(x)
    return ripemd160.digest()

def trunks(l: Iterable, n: int) -> Iterator:
    return zip(*[iter(l)] * n)

def concat(a: bytes, b: bytes) -> bytes:
    return bytes(a) + bytes(b)

def compose(*fs: Iterable[Callable]):
    def _(*args, **kwargs):
        return reduce(
            lambda x, y: y(x),
            fs,
            kwargs or args
        )
    return _

def to_sha256int(m: str):
    return int.from_bytes(sha256(m.encode()).digest(), 'big')

class EnumDict(dict):
    def __getattr__(self, k):
        '''
        Support Lazy
        '''
        value = self.get(k)
        if callable(value):
            value = value()
        if type(value) is dict:
            return EnumDict(value)
        else:
            return value

    def __setattr__(self, k, v):
        self.update({k: v})
        return self

    def __contains__(self, v):
        return v in self.values()

def parse_lv_format(b):
    i = 0
    j = i + 4
    ret = []
    while j <= len(b):
        l = int.from_bytes(bytes(b[i:j]), "big")
        i = j
        j = j + l
        v = bytes(b[i:j])
        ret.append(v)
        i = j
        j = i + 4
    return ret

def CF2Bytes(cf, l=32):
    return cf.value.to_bytes(l, byteorder="big")

def to_u32s(a: int, endian="little"):
    l = 32

    def split():
        carry = a
        while carry != 0:
            yield carry % 2**32
            carry = carry // (2 ** 32)

    return {
        "big": list(split())[::-1],
        "little": list(split())
    }[endian]

def from_u32s(a: list, endian="little"):
    ret = 0
    pos = 0
    if endian == "big":
        a = a[::-1]
    for i in a:
        ret += i * (2**(32*pos))
        pos += 1
    return ret

def from_u64s(a: list, endian="little"):
    ret = 0
    pos = 0
    if endian == "big":
        a = a[::-1]
    for i in a:
        ret += i * (2**(64*pos))
        pos += 1
    return ret

def to_u64s(a: int, endian="little"):
    l = 64

    def split():
        carry = a
        while carry != 0:
            yield carry % 2**64
            carry = carry // (2 ** 64)

    return {
        "big": list(map(hex, split()))[::-1],
        "little": list(map(hex, split()))
    }[endian]


import uuid

import pylsl as lsl
import numpy as np
from pylsl.pylsl import fmt2string, string2fmt

from .. import TIME_AXIS
import xml.etree.ElementTree as ET

string2fmt['float64'] = string2fmt['double64']
LSL_TIME_DIMENSION_ID = 0

class FixedStreamInfo(lsl.StreamInfo):
    def as_xml(self):
        return lsl.pylsl.lib.lsl_get_xml(self.obj).decode('utf-8', 'ignore')

class FixedStreamInlet(lsl.StreamInlet):
    def info(self, timeout=lsl.pylsl.FOREVER):
        errcode = lsl.pylsl.c_int()
        result = lsl.pylsl.lib.lsl_get_fullinfo(self.obj,
                                                lsl.pylsl.c_double(timeout),
                                                lsl.pylsl.byref(errcode))
        lsl.pylsl.handle_error(errcode)
        return FixedStreamInfo(handle=result) # StreamInfo(handle=result)

def convert_lsl_format_to_numpy(lsl_channel_format: int):
    return fmt2string[lsl_channel_format]

def convert_numpy_format_to_lsl(numpy_channel_format: np.dtype):
    return string2fmt[str(numpy_channel_format)]

def create_lsl_outlet(name, frequency, channel_format,
                      channel_labels, channel_types, type=''):
    # Create StreamInfo
    channel_count = len(channel_labels)
    source_id = str(uuid.uuid4())
    info = FixedStreamInfo(
        name=name, type=type, channel_count=channel_count,
        nominal_srate=frequency, channel_format=channel_format,
        source_id=source_id)

    # Add channel labels
    desc = info.desc()
    channels_tag = desc.append_child(name='channels')
    for label, channel_type in zip(channel_labels, channel_types):
        single_channel_tag = channels_tag.append_child(name='channel')
        single_channel_tag.append_child_value(name='label', value=label)
        single_channel_tag.append_child_value(name='type', value=channel_type)

    # Create outlet
    return lsl.StreamOutlet(info)

def _transpose_if_need_be(ndarray: np.ndarray):
    """ Just a helper function for the next two conversion functions """
    if LSL_TIME_DIMENSION_ID != TIME_AXIS:
        return ndarray.T
    else:
        return ndarray

def convert_lsl_chunk_to_numpy_array(lsl_chunk, dtype=None):
    """
    An LSL chunk is a list of lists. Converting it to a numpy array and vice versa is obviously a trivial matter not
    worthy of a designated function. What *does* need taking care of, is whether we should transpose the numpy array or
    not. In LSL time is the first dimension. We might or might not adhere to this convention, which is reflected in the
    TIME_AXIS constant from the base package.
    """
    ndarray = np.array(lsl_chunk).astype(dtype=dtype)
    return _transpose_if_need_be(ndarray)

def convert_numpy_array_to_lsl_chunk(ndarray):
    ndarray = _transpose_if_need_be(ndarray)
    return ndarray.tolist()

convert_numpy_array_to_lsl_chunk.__doc__ = convert_lsl_chunk_to_numpy_array.__doc__

def read_channel_labels_from_info(info: lsl.StreamInfo):
    info_xml = info.as_xml()
    rt = ET.fromstring(info_xml)
    channels_tree = rt.find('desc').findall("channel") or rt.find('desc').find("channels").findall(
                                            "channel")
    labels = [(ch.find('label') if ch.find('label') is not None else ch.find('name')).text
                                                                        for ch in channels_tree]
    # channels_tag = info.desc().child('channels')
    # if channels_tag.empty():
    #     return None
    # else:
    #     # TODO: this is hard to read. Write a generator for children with a given name in helpers
    #     labels = list()
    #     types = list()
    #     single_channel_tag = channels_tag.child(name="channel")
    #     for channel_id in range(info.channel_count()):
    #         labels.append(single_channel_tag.child_value(name='label'))
    #         types.append(single_channel_tag.child_value(name='type'))
    #         single_channel_tag = single_channel_tag.next_sibling(name='channel')
    #     return labels, types
    types = []
    for l in labels:
        types.append('eeg')
    labels = [l[:-3] for l in labels]
    return labels, types


from collections import OrderedDict
from datetime import date, timedelta
import pygsheets
from typing import List, Tuple, Union
import pybraryexceptions

# worksheet is named LIB
# colheaders are named LIBHEADERS
class Pybrary:

    def __init__(self, authfile: str, libraryname: str, librarypage: int, permheaders: List[str], checkoutlimit: float):
        self.authfile = authfile
        self.libraryname = libraryname
        self.librarypage = librarypage
        self.permheaders = permheaders
        self.checkoutlimit = checkoutlimit

        def get_library_info():
            gc = pygsheets.authorize(service_account_file=self.authfile)
            sh = gc.open(self.libraryname)
            lib = sh[self.librarypage]
            lib_headers = OrderedDict()
            fields = lib.get_row(1, include_tailing_empty=False)
            for idx, field in enumerate(fields):
                # Worksheet is 1 indexed
                lib_headers[field] = idx + 1
            return lib, lib_headers
        self.lib, self.lib_headers = get_library_info()

    def get_property_colnum(self, prop: str) -> int:
        try:
            return self.lib_headers[prop]
        except KeyError:
            raise pybraryexceptions.NonexistentProperty(prop)

    def get_book_row(self, booktitle):
        title_col = self.get_property_colnum('Title')
        query = self.lib.find(booktitle, matchEntireCell=True, cols=(title_col, title_col))
        if len(query) == 0:
            print("No book with that title found.")
        elif len(query) > 1:
            print("Multiple books with that title found.")
        row_num = query[0].row
        return row_num

    def get_book(self, booktitle, returnas='matrix'):
        """
        Returns the information of a book, specified by its Title
        :param booktitle: The title of the book to look up
        :param returnas: The data structure type to return
        :type returnas: Union['matrix', 'range']
        :return: A list of all book information or the Range of the cells in which it is located.
        :rtype: Union[List, Range]
        """
        book_row = self.get_book_row(booktitle)
        book = self.lib.get_row(book_row, returnas=returnas, include_tailing_empty=False)
        return book

    def get_book_property(self, book_title: str = None, book_row: int = None, prop: str = None) -> str:
        """
        Returns a specific property for a given book.
        :param book_title: The title of the book. Used for lookup when the row is not specified
        :param book_row: The row where the book is located. Used for lookup when title is not specified.
        :param prop: The name of the property to lookup
        :return: The information specified by the book row and property column
        :rtype: str
        """
        if book_title:
            row_num = self.get_book_row(book_title)
        elif book_row:
            row_num = book_row
        col_num = self.get_property_colnum(prop)
        prop_value = self.lib.get_value((row_num, col_num))
        return prop_value

    def check_out_book(self, book_title, *addinfo):
        """
        :param book_title:
        :param addinfo: suID, suNet, cellPhone, address
        """
        '''
        #TODO: *addInfo should be well formed (includes all values)
        try:
            validateAddInfo()
        except INVALID
            book = getBookInfo(bookTitle, 'range')
        '''
        book = self.get_book(book_title, 'range')
        book_row = book.start_addr[0]
        perm_book_info = []
        for prop in self.permheaders:
            prop_value = self.get_book_property(book_row=book_row, prop=prop)
            perm_book_info.append(prop_value)
        checkout_info = self.get_checkout_info(perm_book_info, *addinfo)
        book.update_values([checkout_info])
        print(book_title + " has been checked out.")

    def get_check_out_range(self) -> Tuple[str, str]:
        """
        Calculates the due date of the book based upon the specified by self.checkoutlimit.
        The calculation is based on the current date and the time delta uses weeks.
        :return: The date out and date due
        :rtype: Tuple[str, str]
        """
        today = date.today()
        date_out = today.strftime("%d %B %Y")
        date_due = today + timedelta(weeks=self.checkoutlimit)
        date_due = date_due.strftime("%d %B %Y")
        return date_out, date_due

    def get_checkout_info(self, perm_book_info, *addinfo):
        checkout_info = []
        for info in perm_book_info:
            checkout_info.append(info)
        for info in addinfo:
            checkout_info.append(info)
        date_out, date_due = self.get_check_out_range()
        checkout_info.append(date_out)
        checkout_info.append(date_due)
        checkout_info.append('True')
        return checkout_info

    def renew_check_out(self, book_title):
        book = self.get_book(book_title, 'range')
        book_row = book.start_addr[0]
        due_date_col = self.get_property_colnum('Date Due')
        date_due = self.get_check_out_range()[1]
        self.lib.update_value((book_row, due_date_col), str(date_due))
        print(book_title + " is now due on " + date_due + ".")

    def check_in_book(self, book_title):
        book = self.get_book(book_title, 'range')
        book_row = book.start_addr[0]
        perm_book_info = []
        for prop in self.permheaders:
            prop_value = self.get_book_property(book_row=book_row, prop=prop)
            perm_book_info.append(prop_value)
        checkin_info = self.get_check_in_info(perm_book_info)
        book.update_values([checkin_info])
        print(book_title + " has been checked in.")

    def get_check_in_info(self, perm_book_info) -> list:
        """
        Creates a list of all information needed to check in a book.
        :param perm_book_info: Information about the book which stays constant between checkin
        :return: A list combining the perm_book_info and empty string entries corresponding to non-permanent fields
        """
        # -1 Corresponds to isCheckedOut Field
        extraneous_field_len = len(self.lib_headers) - len(self.permheaders) - 1
        checkin_info = perm_book_info + [''] * extraneous_field_len
        checkin_info.append('False')
        return checkin_info