# Licensed under a 3-clause BSD style license - see LICENSE.rst

import os
import re

from astropy import log
from astropy.io import fits
import numpy as np
from scipy.optimize import minimize

from sofia_redux.toolkit.utilities.fits import add_history_wrap, hdinsert, kref
from sofia_redux.toolkit.image.adjust import frebin
from sofia_redux.toolkit.image.warp import warp_image, PolynomialTransform

from sofia_redux.instruments.forcast.distcorr_model import distcorr_model
from sofia_redux.instruments.forcast.getpar import getpar
from sofia_redux.instruments.forcast.peakfind import peakfind
from sofia_redux.instruments.forcast.readmode import readmode

addhist = add_history_wrap('Distortion')

__all__ = ['default_pinpos', 'get_pinpos',
           'find_pixat11', 'update_wcs', 'transform_image',
           'rebin_image', 'frame_image', 'find_source',
           'undistort']

def default_pinpos():
    """
    Default values of the model and instrument points to be warped and
    fitted.  Not tested or recommended for use with real FORCAST data.

    Returns
    -------
    dict
        model -> numpy.ndarray
            N(x, y) model reference positions of shape (N, 2)
        pins -> numpy.ndarray
            N(x, y) pin positions of shape (N, 2)
        nx, ny -> int
            define number of pixels for both pins and image in x and y
        dx, dy -> float
            model x, y spacing
        angle -> float
            clockwise rotation of the model about the center
            in degrees.
        order -> int
            order to be used if using the "polynomial" method in
            sofia_redux.instruments.forcast.undistort based upon this model.
    """
    # The following model defines (6 x 6) pins on a (256 x 256) image
    x_model = [13, 58, 104, 149, 194, 239, 13, 58, 104, 149, 194, 239, 13,
               58, 104, 149, 194, 239, 13, 58, 104, 149, 194, 239, 13, 58,
               104, 149, 194, 239, 13, 58, 104, 149, 194, 239]
    y_model = [242, 242, 242, 242, 242, 242, 196, 196, 196, 196, 196, 196,
               151, 151, 151, 151, 151, 151, 106, 106, 106, 106, 106, 106,
               60, 60, 60, 60, 60, 60, 15, 15, 15, 15, 15, 15]
    x_pos = [6, 54, 102, 150, 198, 247, 5, 54, 102, 150, 198, 247, 5,
             54, 102, 150, 198, 246, 5, 54, 102, 150, 198, 247, 6, 54,
             102, 150, 199, 247, 7, 55, 103, 151, 199, 248]
    y_pos = [242, 243, 243, 243, 242, 241, 197, 199, 199, 199, 198,
             196, 151, 152, 152, 152, 151, 151, 105, 106, 106, 106, 106,
             104, 58, 59, 60, 59, 58, 57, 10, 10, 11, 11, 10, 9]
    x_model = np.asarray(x_model, float)
    y_model = np.asarray(y_model, float)
    x_pos = np.asarray(x_pos, float)
    y_pos = np.asarray(y_pos, float)
    return {
        'model': np.stack((x_model, y_model), axis=1),
        'pins': np.stack((x_pos, y_pos), axis=1),
        'nx': 256, 'ny': 256,
        'dx': 1.0, 'dy': 1.0,  # The ratio dy/dx is used in rebin_image()
        'angle': 0.0,  # not used but will appear in header under PIN_MOD
        'order': 3,  # only used when undistorting using 'polynomial'
    }

def get_pinpos(header, pinpos=None, rotate=False):
    """
    Get the pinhole model and coefficients and update header.

    The majority of this code sanity checks pin positions and
    model positions.  If the user has not supplied the pinpos
    dict, one will be retrieved from the drip configuration or
    from a default set defined in this file (not recommended).
    Note that if the header contains the PIN_MOD keyword it
    will override 'dx', 'dy', 'angle', and 'order' from any
    other source.

    The user may optionally rotate the image by NODANGLE in the
    header.  If the angle is non-zero, the model will be
    rotated around the center of the image and be rescaled by a
    factor of two.  Note that we are not interested in scaling
    or offsets at this point as only the relative warping
    between points is important.  Offsets and scalings will be
    determined during the WCS correction and re-binning steps.

    The PIN_MOD keyword in the header will be updated to contain
    values as a string in the format '[dx,dy,angle,order]' where
    all values are described under the pinpos parameter.

    Parameters
    ----------
    header : astropy.io.fits.header.Header
        FITS header that will may be used to determine pinhole model
        coefficients or will be updated with them
    pinpos : dict or str or None
        if pinpos == 'default' then the output pin positions will
        be retried from default_pinpos(). If pinpos is None, the pin
        positions will be retrieved according to the drip
        configuration.  Otherwise, the user should explicitly define
        pinpos as a dict with the following keys and values:

            model -> numpy.ndarray
                N(x, y) model reference positions of shape (N, 2)
            pins -> numpy.ndarray
                N(x, y) pin positions of shape (N, 2)
            nx, ny -> int
                define number of pixels for both pins and image in x and y
            dx, dy -> float
                model x, y spacing
            angle -> float
                clockwise rotation of the model about the center in
                degrees.  This is not required for anything but the
                PIN_MID keyword in the header will be updated with
                this value.
            order -> int
                order to be used if using the "polynomial" method in
                sofia_redux.instruments.forcast.undistort based upon
                this model.

    rotate : bool, optional
        rotate the model by 'NODANGLE' in the header

    Returns
    -------
    dict or None
        pinpos if validated else None.  If rotation occurred then
        pinpos['model'] will be updated.  Keys and values are
        described above under the `pinpos` parameter.
    """

    if pinpos is None:
        pinpos = distcorr_model()
    elif isinstance(pinpos, str):
        if pinpos.strip().lower() == 'default':
            pinpos = default_pinpos()
            log.info("default values have been used for pinholes")
        elif os.path.isfile(pinpos):
            pinpos = distcorr_model(pinhole=pinpos)
    fail, msg = False, ''
    if not isinstance(pinpos, dict):
        fail, msg = True, f"pinpos must be {dict}, None, or 'default'"
    elif 'model' not in pinpos or 'pins' not in pinpos:
        fail, msg = True, "missing model or pin positions"
    elif not isinstance(pinpos['model'], np.ndarray):
        fail, msg = True, "model is not an array"
    elif not isinstance(pinpos['pins'], np.ndarray):
        fail, msg = True, "pins is not an array"
    elif len(pinpos['model'].shape) != 2 or len(pinpos['pins'].shape) != 2:
        fail, msg = True, "model and pins must be of shape (N, 2)"
    elif pinpos['model'].shape != pinpos['pins'].shape:
        fail, msg = True, "model shape does not match pins shape"
    if fail:
        log.error(msg)
        addhist(header, 'correction was not applied (Invalid pinpos)')
        return
    pp = pinpos.copy()

    pin_model_read = getpar(
        header, 'PIN_MOD', comment='pinhole model coeffs')
    if pin_model_read is None:
        dx, dy = pp.get('dx'), pp.get('dy')
        if None in [dx, dy]:
            addhist(header, 'correction was not applied (Invalid coeffs)')
            log.error("could not determine pinhole coefficients")
            return
        order = pp.get('order', -1)
        # angle is not required, but will be put in the header
        angle = pp.get('angle', 0.0)
        hdinsert(header, 'PIN_MOD', f'[{dx},{dy},{angle},{order}]',
                 comment='pinhole model coeffs', refkey=kref)
    else:
        # pp will be updated with header PIN_MOD coefficients
        pinmod = re.split(r'[\[\],]', pin_model_read)
        try:
            pinmod = [float(x) for x in pinmod if x != '']
        except (ValueError, TypeError):
            addhist(header, 'correction was not applied (Invalid coeffs)')
            log.error("invalid PIN_MOD values in header")
            return
        if len(pinmod) != 4:
            addhist(header, 'correction was not applied (Invalid coeffs)')
            log.error("invalid PIN_MOD values in header")
            return
        pp['dx'], pp['dy'] = pinmod[0], pinmod[1]
        pp['angle'] = pinmod[2]
        pp['order'] = int(pinmod[3])

    angle = getpar(header, 'NODANGLE', dtype=float, default=0)
    if rotate and angle != 0:
        # Note (x, y) FITS convention, not numpy (y, x)
        img_size = np.array([header['NAXIS1'], header['NAXIS2']])
        center = (img_size - 1) / 2
        model = pp['model'].copy()
        x, y = model[..., 0] - center[0], model[..., 1] - center[1]
        radians = np.deg2rad(angle)
        cos_a, sin_a = np.cos(radians), np.sin(radians)
        xr = (cos_a * x) - (sin_a * y)
        yr = (sin_a * x) + (cos_a * y)
        model[..., 0], model[..., 1] = xr + center[0], yr + center[1]
        pp['model'] = model
        addhist(header, f'Images rotate by NODANGLE={angle}')

    return pp

def find_pixat11(transform, x0, y0, epsilon=1e-8,
                 xrange=(0, 255), yrange=(0, 255),
                 method=None, maxiter=None, verbose=False,
                 direct=True):
    """
    Calculate the position of x0, y0 after a transformation

    Also calculate the position of x1 (= x0 + 1), y1 (= y0 + 1)
    after the transformation in order to determine pixel scaling
    and offsets.

    If we have a transform that can be directly inverted, then
    the solution is simple and we can return an exact solution.
    In this case the all optional arguments are ignored aside
    from eps which will determine the number of decimal places
    in the output (None will not limit precision).

    Otherwise, we will need to perform some type of minimization.
    The default minimization method, 'TNC' is a truncated Newton
    algorithm used to minimize a function subject to bounds.  It
    is suitable for the purposes of
    sofia_redux.instruments.forcast.undistort.  If you wish
    to solve an unbounded problem, leave method = None to allow
    scipy.optimize.minimize to select an appropriate method or
    set your own.

    Parameters
    ----------
    transform : PolynomialTransform or function or object
        as returned by warp_image with get_transform=True or a user defined
        function or object.  Should take in (y, x) coordinates as an argument
        and return the transformed (y, x) coordinates.
    x0 : float
        input x coordinate
    y0 : float
        input y coordinate
    epsilon : float, optional
        If a polynomial transform was used, an iterative method is
        used in place of inversion.  The iteration will be terminated
        after the tolerance is lower than eplison.
    xrange : tuple of float, optional
        (xmin, xmax) range of x values to search
    yrange : tuple of float, optional
        (ymin, ymax) range of y values to search
    method : str, optional
        optimization method.  See scipy.optimize.minimize for
        full list of available options.
    maxiter : int, optional
        terminitate search after this many iterations
    verbose : bool, optional
        if True, print convergence messages
    direct : bool, optional
        Attempts direct inversion if True, otherwise use a
        minimization routine.

    Returns
    -------
    dict
        {x0 -> float, y0 -> float,
         x1 -> float, y1 -> float,
         x1ref -> float, y1ref -> float}
    """
    c0 = np.array([y0, x0])
    if epsilon is None:
        decimals = 0
        eps = 1e-8
    else:
        decimals = -int(np.log10(epsilon * 10))
        decimals = 0 if decimals < 0 else decimals
        eps = epsilon

    # Check if we can do direct inversion
    if direct and isinstance(transform, PolynomialTransform):
        c1 = transform(c0, inverse=True)
        c1p1 = transform(c1 + 1)
        return {'x0': x0, 'y0': y0,
                'x1ref': np.round(float(c1[1]), decimals=decimals),
                'y1ref': np.round(float(c1[0]), decimals=decimals),
                'x01': np.round(float(c1p1[1]), decimals=decimals),
                'y01': np.round(float(c1p1[0]), decimals=decimals)}

    # Otherwise we have to do minimization
    if xrange is None and yrange is None:
        bounds = None
    else:
        bounds = [yrange, xrange]
        if method is None:
            method = 'TNC'

    def transform_distance(params):
        tyx = transform(np.array([params[0], params[1]]))
        return ((tyx - c0) ** 2).sum()

    options = {'disp': verbose}
    if isinstance(maxiter, int):
        options['maxiter'] = maxiter
    p = minimize(transform_distance, c0.copy(), method=method,
                 tol=eps, bounds=bounds, options=options)
    if not p.success:
        try:
            msg = p.message.decode()
        except AttributeError:
            msg = str(p)

        log.error(f"minimization failed: {msg}")
        return
    y1ref, x1ref = p.x

    # Find the pixel in the initial image that is at (x1ref+1, y1ref+1)
    # in the resulting image
    y01, x01 = transform(np.array([y1ref + 1, x1ref + 1]))

    return {'x0': x0, 'y0': y0,
            'x1ref': np.round(float(x1ref), decimals=decimals),
            'y1ref': np.round(float(y1ref), decimals=decimals),
            'x01': np.round(float(x01), decimals=decimals),
            'y01': np.round(float(y01), decimals=decimals)}

def update_wcs(header, transform, eps=None):
    """
    Update the WCS in the header according to the transform

    Parameters
    ----------
    header : astropy.io.fits.header.Header
        FITS header to update
    transform
        A function that transforms input coordinates to output
        coordinates.  numpy.ndarray (2, N) -> (2, N)
    eps : float, optional
        precision

    Returns
    -------
    dict
        contains output from find_pixat11 with an additional
        key, 'update_cdelt'.  update_cdelt will
        be True if CDELT1 and CDELT2 were successfully
        updated and False otherwise.
    """
    if not isinstance(header, fits.header.Header):
        return

    if 'CRPIX1' not in header or 'CRPIX2' not in header:
        addhist(header,
                'CRPIX1 or CRPIX2 are not in header. Skipping WCS update')
        return

    crpix1 = header.get('CRPIX1', 1.0)
    crpix2 = header.get('CRPIX2', 1.0)
    addhist(header, f'CRPIX from stack= [{crpix1},{crpix2}]')

    log.info("updating WCS")
    dxy = find_pixat11(transform, crpix1 - 1, crpix2 - 1, epsilon=eps)
    if dxy is None:
        log.warning('CRPIX values not found after transform')
        return

    addhist(header, f"CRPIX = [{dxy['x0'] + 1},{dxy['y0'] + 1}]")
    addhist(header, f"CRPIX after transform = "
                    f"[{dxy['x1ref'] + 1},{dxy['y1ref'] + 1}]")
    hdinsert(header, 'CRPIX1', dxy['x1ref'] + 1, refkey=kref)
    hdinsert(header, 'CRPIX2', dxy['y1ref'] + 1, refkey=kref)

    # Update CDELT
    cd1, cd2 = header.get('CDELT1', -1), header.get('CDELT2', -1)
    addhist(header, f"CDELT from stack= [{cd1},{cd2}]")
    addhist(header, f"CROT from stack= [ - ,{header.get('CROTA2', -1)}]")
    for key in ['CROTA2', 'CDELT1', 'CDELT2']:
        if key not in header:
            dxy['update_cdelt'] = False
            addhist(header, 'CROTA2, CDELT1 or CDELT2 are not in '
                            'header. Using default Platescale')
            break
    else:
        dxy['update_cdelt'] = True
        addhist(header, f"Ref CRPIX+1 = [{dxy['x01'] + 1},{dxy['y01'] + 1}]")
        header['CDELT1'] *= (dxy['x01'] - dxy['x0'])
        header['CDELT2'] *= (dxy['y01'] - dxy['y0'])

    return dxy

def transform_image(data, xin, yin, xout, yout, header=None, variance=None,
                    order=4, get_dxy=False, extrapolate=False):
    """
    Transform an image and update header using coordinte point mapping

    Transforms an image such that points in (yin, xin) are warped to
    (yout, xout) in the output image.  If a header is supplied, any
    WCS information will be updated accordingly.  Note that order is
    only important if you are doing a polynomial warp.

    Parameters
    ----------
    data : numpy.ndarray
        input image (nrow, ncol)
    xin : array-like
        warping input x-coordinates
    yin : array-like
        warping input y-coordinates
    xout : array-like
        warping output x-coordinates
    yout : array-like
        warping output y-coordinates
    variance : numpy.ndarray, optional
        variance array to update in parallel with the data array
    header : astropy.io.fits.header.Header, optional
        FITS header to update WCS
    order : int, optional
        Order of the polynomial used to warp the image.
    get_dxy : bool, optional
        If True
    extrapolate : bool, optional
        If `False`, values outside of the rectangular range of `xout` and
        `yout` will be set to `cval`.

    Returns
    -------
    2-tuple or 3-tuple
        - warped output image (nrow, ncol)
        - warped variance (nrow, ncol)
        - dxy, optional output from update_wcs
    """
    image, transform = warp_image(
        data.copy(), xin, yin, xout, yout, order=order, mode='constant',
        cval=np.nan, get_transform=True, extrapolate=extrapolate)
    dovar = isinstance(variance, np.ndarray) and variance.shape == data.shape
    var = variance.copy() if dovar else None
    if not dovar and variance is not None:
        msg = "variance not propagated (invalid variance at transform_image)"
        addhist(header, msg)
        log.error(msg)
    if dovar:
        var = warp_image(var, xin, yin, xout, yout, order=order,
                         mode='constant', cval=np.nan, extrapolate=extrapolate)
    addhist(header, f'correction model uses order {order}')
    log.info(f"distortion solution order: {order}")
    dxy = update_wcs(header, transform)

    if get_dxy:
        return image, var, dxy
    else:
        return image, var

def rebin_image(image, factor, header=None, variance=None, platescale=None):
    r"""
    Rebin the image to square pixels

    Here factor represents dy/dx (pixel height/width).  If the pixels
    were square, then 11\*dx == 11\*dy, or nx\*dy == ny\*dy.  The smaller
    the pixels, the larger the number of them across the array for the
    same angular distance.  We can make the pixels square by re-binning
    such that the above equation holds, but must assume one dimension
    is the reference with 256 pixels.

    We can adopt the principle that the re-binned array should be as
    close to 256 x 256 as possible.  In that case nx'\*ny' == 256\*256
    and nx' == ny' \* (dy/dx).

    Parameters
    ----------
    image : numpy.ndarray
        input image with rectangular pixels (nrow, ncol)
    factor : float
        the ration dy/dx (pixel height/width)
    header : astropy.io.fits.header.Header
        FITS header containing WCS.  Values will be updated and
        HISTORY messages will be written.
    variance : numpy.ndarray
        variance array to update in parallel
    platescale : float
        if set, CDELT will be set to this value in header WCS.
        Should be provided as arc seconds.

    Returns
    -------
    2-tuple
        numpy.ndarray : the rebinned image (nrow, ncol)
        numpy.ndarray : the rebinned variance (nrow, ncol)
    """
    ny = int(np.round(np.sqrt(np.product(image.shape) / factor)))
    nx = int(np.round(ny * factor))
    rebinned = frebin(image.copy(), (ny, nx), total=True)

    dovar = isinstance(variance, np.ndarray) and variance.shape == image.shape
    if not dovar and variance is not None:
        addhist(header, 'variance not propagated (invalid variance at rebin)')
        log.error("variance shape does not match data shape")
    var = variance.copy() if dovar else None
    if dovar:
        # Rebin variance as standard deviation so that the
        # flux-conservation factor gets applied appropriately
        var = frebin(np.sqrt(var), (ny, nx), total=True) ** 2

    if header is None:
        return rebinned, var
    # The reference when rebinning an immage between the initial and
    # the final image is the corner of the image, not the first pixel
    # position.  The corner is 0.5 pixels from the pixel number 0.
    # NOTE: CRPIX values have +1 added to pixel positions, so what
    # we did was the same as x = crpix - 1 + 0.5, and then the same
    # in reverse.
    addhist(header, "new image size: %s x %s" % (nx, ny))
    log.info("new image size: %s x %s" % (nx, ny))
    x = header['CRPIX1'] - 0.5 - (image.shape[1] / 2)
    header['CRPIX1'] = x * (nx / image.shape[1]) + 0.5 + (nx / 2.0)
    y = header['CRPIX2'] - 0.5 - (image.shape[0] / 2)
    header['CRPIX2'] = y * (ny / image.shape[0]) + 0.5 + (ny / 2.0)

    if 'CDELT1' not in header or 'CDELT2' not in header:
        return rebinned, var
    if platescale is not None:
        deg = platescale / 3600.0
        header['CDELT1'] = -deg if header['CDELT1'] < 0 else deg
        header['CDELT2'] = -deg if header['CDELT2'] < 0 else deg
    else:
        header['CDELT1'] *= image.shape[1] / nx
        header['CDELT2'] *= image.shape[0] / ny
    addhist(header, 'CDELT after rebin = [%f,%f]' % (
        header['CDELT1'], header['CDELT2']))

    return rebinned, var

def frame_image(image, shape, header=None, variance=None, border=0, wcs=True):
    """
    Frame an image in the center of a new image and add border

    The size of the new image must be larger than the original in
    both dimensions.  There is a hard-coded border minimum of 5
    pixels.  Unfilled pixels are set to NaN.

    Parameters
    ----------
    image : numpy.ndarray
        input data array (nrow, ncol) to be inserted in the
        center of the output image
    header : astropy.io.fits.header.Header
        input FITS header.  WCS will be updated and HISTORY messages
        will be added.
    shape : 2-tuple of int
        shape of the output image without the border (nrow, ncol)
        NOTE - Python (y, x) order please
    variance : numpy.ndarray
        variance array (nrow, ncol) to update in parallel with image.
    border : int, optional
        default border to add around image.  This will only be applied
        if 'BORDER' is in neither the header nor the drip configuration.
        The minimum border allowed is 5.
    wcs : bool, optional
        If True update the WCS in the header.  Will only update keywords
        that already exist.

    Returns
    -------
    2-tuple
        numpy.ndarray : output data (nrow?, ncol?)
        numpy.ndarray : output variance (nrow?, ncol?)

    Notes
    -----
    The output array shapes are not definitively predicted beforehand.
    `border` merely indicates the minimum size of the output arrays.  If
    the distortion results in an output image that is larger than
    (ny, nx) + 2*border, the border will be expanded to ensure that no
    errors are encountered.  `border` should be set in the drip
    configuration file as that value will override any value supplied to
    sofia_redux.instruments.forcast.undistort or extracted from the header.
    """
    dovar = isinstance(variance, np.ndarray) and variance.shape == image.shape
    var = variance.copy() if dovar else None
    if not dovar and variance is not None:
        msg = 'variance not propagated (invalid variance at frame_image)'
        addhist(header, msg)
        log.error(msg)

    if header is None:
        header = fits.header.Header()
    comment = 'additional border pixels'
    border = getpar(
        header, 'BORDER', comment=comment, dtype=int, default=border)

    shape = np.array(shape).astype(int)
    imshape = np.array(image.shape).astype(int)
    minborder = int(np.ceil((imshape - shape) / 2).max())
    if minborder > border:
        msg = "expanding border from %s to %s" % (border, minborder)
        addhist(header, msg)
        log.info(msg)
        border = minborder
        if 'BORDER' in header:
            header['BORDER'] = border

    shape += 2 * border
    minyx = (shape // 2) - (imshape // 2)
    framed = np.full(shape, np.nan, dtype=image.dtype)
    framed[minyx[0]: minyx[0] + imshape[0],
           minyx[1]: minyx[1] + imshape[1]] = image.copy()

    if dovar:
        newvar = np.full(shape, np.nan, dtype=var.dtype)
        newvar[minyx[0]: minyx[0] + imshape[0],
               minyx[1]: minyx[1] + imshape[1]] = var.copy()
        var = newvar

    header['NAXIS1'] = shape[1]
    header['NAXIS2'] = shape[0]
    if not wcs:
        return framed, var

    if 'CRPIX1' and 'CRPIX2' in header:
        header['CRPIX1'] += minyx[1]
        header['CRPIX2'] += minyx[0]
        addhist(header, "CRPIX after border = [%f,%f]" % (
            header['CRPIX1'], header['CRPIX2']))
    if 'CRVAL1' and 'CRVAL2' in header:
        addhist(header, 'CRVAL = [%f,%f]' % (
            header['CRVAL1'], header['CRVAL2']))

    return framed, var

def find_source(image, header):
    """
    Find single peak in image and update SRCPOS in header

    Parameters
    ----------
    image : numpy.ndarray
        input image (nrow, ncol)
    header : astropy.io.fits.header.Header, optional
        FITS header to update

    Returns
    -------
    None
    """
    search = np.zeros_like(image)
    clip = getpar(header, 'BORDER', update_header=False, default=0,
                  dtype=int) + 10
    search[clip: -clip, clip: -clip] = image[clip: -clip, clip: -clip]
    pos = peakfind(search, npeaks=1, silent=True, coordinates=True)

    if pos is None or len(pos) != 1:
        return
    hdinsert(header, 'SRCPOSX', pos[0][0],
             comment='Source x-position', refkey=kref)
    hdinsert(header, 'SRCPOSY', pos[0][1],
             comment='Source y-position', refkey=kref)

def undistort(data, header=None, pinhole=None, rotate=False,
              variance=None, default_platescale=0.768, extrapolate=False):
    """
    Corrects distortion due to camera optics.

    Resamples data with a polynomial warping calculated from known
    pinhole positions to correct for optical distortions.  Applies
    a border to the image to avoid losing data off the edge of the
    array.  After distortion correction, the WCS keywords are
    updated.  This function is typicalled called without the
    `rotate` parameter and `pinpos` provided by the output of
    discorr_model.  The border width can be set in the `border`
    parameter in the configuration file; default is 128 pixels.
    The border pixels are set to NaN to mark them as non-data.

    Parameters
    ----------
    data : numpy.ndarray
        Input data array (nrow, ncol)
    header : astropy.io.fits.header.Header, optional
        Input header; will be updated with a HISTORY message
    pinhole : dict or str, optional
        dictionary defining the pinhole model or path to a pinhole
        file.  If None, will generate a default model based on the
        drip configuration file.  If 'default' will use model
        defined in the default_pinpos() function.  See get_pinpos()
        for a description of keys and values.
    rotate : bool, optional
        If True, distortion model data will be rotated by NODANGLE
        (in header) before applying distortion correction.
    variance : numpy.ndarray, optional
        Variance array (nrow, ncol) to update in parallel with the
        data array
    default_platescale : float, optional
        If set, CDELT1 and CDELT2 will be set to this value. Set to
        None to automatically update.
    extrapolate : bool, optional
        If `False`, values outside of the rectangular range of `xout` and
        `yout` will be set to `cval`.

    Returns
    -------
    2-tuple
        numpy.ndarray : The distortion-corrected array (nrow, ncol)
        numpy.ndarray : The distortion-corrected variance (nrow, ncol)
    """
    create_header = False
    if not isinstance(header, fits.header.Header):
        create_header = True
        header = fits.header.Header()
    elif 'CRPIX1' not in header or 'CRPIX2' not in header:
        create_header = True

    if not isinstance(data, np.ndarray) or len(data.shape) != 2:
        addhist(header, "correction was not applied (Invalid data)")
        log.error("must provide valid data array")
        return

    dovar = isinstance(variance, np.ndarray) and variance.shape == data.shape
    var = variance.copy() if dovar else None
    if not dovar and variance is not None:
        msg = "variance not propagated (invalid variance)"
        addhist(header, msg)
        log.error(msg)

    if create_header:
        addhist(header, 'Created header')
        log.debug("Creating basic header WCS. Using "
                  "CRPIX=data.shape/2, CDELT=-1.0, CROTA2=0.0")
        header['NAXIS1'] = data.shape[1]
        header['NAXIS2'] = data.shape[0]
        header['CDELT1'] = -1.0
        header['CDELT2'] = -1.0
        header['CRPIX1'] = data.shape[1] / 2
        header['CRPIX2'] = data.shape[0] / 2
        header['CROTA2'] = 0.0

    if header.get('NAXIS1') != data.shape[1]:
        header['NAXIS1'] = data.shape[1]
    if header.get('NAXIS2') != data.shape[0]:
        header['NAXIS2'] = data.shape[0]
    orig_crpix1 = header['CRPIX1']
    orig_crpix2 = header['CRPIX2']

    # Get the pinhole model
    pinhole = get_pinpos(header, pinpos=pinhole, rotate=rotate)
    if pinhole is None:
        log.error("failed to find a valid pinhole model")
        return
    x_model = pinhole['model'][:, 0]
    y_model = pinhole['model'][:, 1]
    x_pos = pinhole['pins'][:, 0]
    y_pos = pinhole['pins'][:, 1]

    # Perform transformation
    corrected_image, var, dxy = transform_image(
        data.copy(), x_pos, y_pos, x_model, y_model, header=header,
        variance=var, order=pinhole['order'], get_dxy=True,
        extrapolate=extrapolate)

    # Rebin image to square pixels
    if default_platescale is None:
        if not isinstance(dxy, dict) or not dxy.get('update_cdelt'):
            default_platescale = 0.768
    factor = pinhole['dy'] / pinhole['dx']
    rebinned, var = rebin_image(
        corrected_image.copy(), factor, header=header, variance=var,
        platescale=default_platescale)

    # Frame the image in an array the size of the original data with
    # an added border (defined in the drip configuration or header)
    # If the border has not been defined, the default is 128 pixels on
    # all sides.
    undistorted, var = frame_image(
        rebinned.copy(), data.shape, header=header, variance=var,
        wcs=isinstance(dxy, dict))

    # Update the product type
    hdinsert(header, 'PRODTYPE', 'UNDISTORTED',
             comment='product type', refkey=kref)

    # If image is a standard and NMC, locate the brightest source
    # and record its position in the basehead.
    mode = readmode(header)
    obstype = getpar(header, 'OBSTYPE', comment='Observation type')
    if mode.lower().strip() == 'nmc' and \
            obstype.lower().strip() == 'standard_flux':
        find_source(undistorted, header)
    log.info("undistorted image shape (x, y): (%i, %i)" %
             (undistorted.shape[1], undistorted.shape[0]))

    # also store the cumulative change to CRPIX
    dcrpix1 = header.get('DCRPIX1', 0.0)
    dcrpix2 = header.get('DCRPIX2', 0.0)
    hdinsert(header, 'DCRPIX1', dcrpix1 + header['CRPIX1'] - orig_crpix1,
             comment='Change in CRPIX before registration', refkey=kref)
    hdinsert(header, 'DCRPIX2', dcrpix2 + header['CRPIX2'] - orig_crpix2,
             comment='Change in CRPIX before registration', refkey=kref)

    return undistorted, var


def mark_negative_words(neg_words, tokenized_sentence):
    # create the HTML code and mark the negative words red in a sentence
    result = ""
    marked = False
    for word in tokenized_sentence:
        space = " "
        if not(word.isalnum()):
            # if the word is not an alphabetic letter, do not add a space
            space = ""
        if word in neg_words:
            # mark the negative word red (in HTML code)
            word = "" + word + ""
            marked = True
        result += space + word
    result = result.strip()
    return result, marked

def make_class_prob(class_prob):
    # create the code for the class probability
    result = ""
    result += "
"
    result += "
"
    result += "
"
    result += "Certainty: " + str(class_prob) + "%"
    result += "

"
    result += "
"
    result += "
"
    result += "
"
    result += "
"
    result += "

"
    result += "
" + "
" + "
"
    return result

def make_reviewed_buttons(cyberbully):
    # make the buttons for tweets that are reviewed
    result = ""
    if cyberbully:
        # if the class is cyberbully make 2 buttons: rephrase button and post anyways button
        result += "<form>\n"
        result += "<button>Rephrase Tweet</button>\n"
        result += "</form>\n"
        result += "<form>\n"
        result += "<button>Post Anyways</button>\n"
        result += "</form>\n"
        return result
    else:
        # if the class is not cyberbully make just 1 button
        result += "<form>\n"
        result += "<button>Post My Tweet!</button>\n"
        result += "</form>\n"

def make_review_tweet(tweet, pred, neg_words, class_prob):
    # make the result after pressing the "review tweet" button
    result = ""
    result += "
Your tweet was: \'" + tweet + "\'
\n"
    cyberbully = False
    if pred != "Not Cyberbullying":
        cyberbully = True
        result += "
Warning you are discriminating based on " + pred + "
\n"
        if len(neg_words) != 0:
            result += "
Please delete, or rephrase the red words
\n"
        else:
            result += "
Please rephrase your Tweet
"
    else:
        result += "
No Cyberbullying was detected!
"
    # make the buttons HTML code
    buttons = make_reviewed_buttons(cyberbully)
    # make the class probabilities HTML code
    class_prob = make_class_prob(class_prob)
    return result, buttons, class_prob


import argparse
import json
import matplotlib.pyplot as plt
import numpy as np

def plot_topo_query_exp(filename):
    results = json.load(open(filename + '.json'))

    for iter, result in results.items():
        max_len = max(len(line) for line in result)
        result = list(filter(lambda e: len(e) == max_len, result)) # get rid of incomplete runs

        result = np.array(result)
        avg_result = np.mean(result, axis=0)

        query_nums = []
        rewards = []
        idx = 0
        while idx < len(avg_result):
            query_nums.append(avg_result[idx + 1])
            rewards.append(avg_result[idx + 2])
            idx += 8

        print(query_nums, rewards)
        plt.plot(query_nums, rewards, marker='+', label=iter + ' iter')

    plt.xlabel('Number of Queries')
    plt.ylabel('Average Reward')
    plt.legend()

    plt.title('Active Learning (Retrain and Rebuild Tree)')
    plt.savefig(filename + ".png", dpi=300, format="png")
    plt.show()
    plt.close()

if __name__ == '__main__':
    parser = argparse.ArgumentParser()

    parser.add_argument('-data', type=str, required=True, help='result json file')
    args = parser.parse_args()

    plot_topo_query_exp(args.data)


import json
import time

from ark import Player, config, TribeLog

from ...webhooks import TribeLogWebhook, InfoWebhook
from .medbrew_station import MedbrewStation
from ._settings import MedbrewStationSettings

with open("settings/settings.json") as f:
    config.ARK_PATH = json.load(f)["main"]["ark_path"]

def main():
    with open("settings/settings.json") as f:
        data = json.load(f)

    name = input("Name of the station to calibrate? INCLUDING NUMBER! For example: brew00\n")

    for s in range(5, -1, -1):
        print(f"Beginning calibration in {s}...", end="\r")
        time.sleep(1)

    player = Player(100, 100, 100, 100)

    grinding = MedbrewStation(
        name,
        player,
        TribeLogWebhook(TribeLog(), data["discord"]["webhook_alert"], data["discord"]["webhook_logs"]),
        InfoWebhook(data["discord"]["webhook_gacha"], ""),
        MedbrewStationSettings.load()
    )

    grinding._spawn_at(grinding.narcoberry_bed)
    for idx, (turn, val) in enumerate(grinding.narco_bench_turns, start=1):
        turn(val, delay=1)
        print(f"Looking at chembench {idx}")
        player.sleep(2)

    grinding._spawn_at(grinding.chembench_bed)

    for turn, val in grinding.gasoline_turns:
        turn(val, delay=1)
        print(f"Looking at gasoline")
        player.sleep(2)

    for turn, val in reversed(grinding.gasoline_turns):
        turn(val * -1, delay=1)

    for idx, (turn, val) in enumerate(grinding.meat_bench_turns, start=1):
        turn(val, delay=1)
        print(f"Looking at chembench {idx}")
        player.sleep(2)

    for idx, (turn, val) in enumerate(grinding.cooker_turns, start=1):
        turn(val, delay=1)
        print(f"Looking at cooker {idx}")
        player.sleep(2)

    grinding._spawn_at(grinding.tintoberry_bed)
    for idx, (turn, val) in enumerate(grinding.cooker_turns_2, start=1):
        turn(val, delay=1)
        print(f"Looking at cooker {idx}")
        player.sleep(2)

if __name__ == "__main__":
    main()


# -*- coding:utf-8 -*-

# Write an algorithm to determine if a number is "happy".
#
# A happy number is a number defined by the following process: Starting with any positive integer, replace the number by the sum of the squares of its digits, and repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle which does not include 1. Those numbers for which this process ends in 1 are happy numbers.
#
# Example:
#
#
# Input: 19
# Output: true
# Explanation:
# 12 + 92 = 82
# 82 + 22 = 68
# 62 + 82 = 100
# 12 + 02 + 02 = 1
#
#

class Solution(object):
    def isHappy(self, n):
        """
        :type n: int
        :rtype: bool
        """
        #自己写的，慢，或许递归好一点？
        '''
        if n == 0: return False
        resultSet = set()
        while True:
            if self.squares(n) == 1:
                return True
            else:
                if n in resultSet:
                    return False
                else:
                    resultSet.add(n)
                    n = self.squares(n)
        '''
        #递归果然好一点
        '''
        if n == 0: return False
        resultSet = set()
        return self.isHappyRec(n, resultSet)

    def isHappyRec(self, n, s):
        if self.squares(n) == 1:
            return True
        else:
            if n in s:
                return False
            else:
                s.add(n)
                return self.isHappyRec(self.squares(n), s)
    '''
        #之前的循环写的不太好，可以把self.squares(n) == 1作为while退出的条件
        if n == 0: return False
        resultSet = set()
        while n != 1:
            if n in resultSet:
                return False
            else:
                resultSet.add(n)
                n = self.squares(n)
        return True

    def squares(self, n):
        result = 0
        while n != 0:
            result += (n % 10) ** 2
            n //= 10
        return result


#!/usr/bin/python
# -*- coding: utf-8 -*-
# @Time    : 2017/5/3 15:32

import os
import time
from api.file_api import get_all_file

class Detect(object):
    # 返回一个目录下的新生成文件的绝对路径
    def __init__(self):
        self.flag = True

    def detector(self, path, sec):
        if not path.endswith('\\' or '/'):
            path = path + '/'
        origin = set(x for x in get_all_file(path))
        time.sleep(sec)
        final = set(x for x in get_all_file(path))
        # origin = set([_f[2] for _f in os.walk(path)][0])
        # time.sleep(sec)
        # final = set([_f[2] for _f in os.walk(path)][0])
        if final.difference(origin):
            self.flag = False
            return list(final.difference(origin))[0]

if __name__ == '__main__':
    d = Detect()
    while d.flag:
        file_name = d.detector('I:\\bishe\\test', 1)
        if file_name:
            print(file_name)
    print('end')


from __future__ import annotations

import calendar
import re
from datetime import datetime as dt
from typing import Any

import click
from typing_extensions import TypedDict

import ckan.model as model
import ckan.plugins.toolkit as tk

RE_PKG = re.compile(r"/dataset/(?P<id>[^/?#]+)")
RE_RES = re.compile(r"/dataset/(?P<pkg_id>[^/?#]+)/resource/(?P<res_id>[^/?#]+)")

GA_TYPE_RES = "resource"
GA_TYPE_PKG = "dataset"

PAGE_PATH = 0
PAGE_VIEWS = 1
RES_DOWNLOADS = -1

DGA_STATS = "dga_ga_stats"

class GaData(TypedDict):
    headers: list[str]
    rows: list[list[str]]

class PresentDate(Exception):
    pass

@click.group("stats", short_help="Google analytic stats")
@click.help_option("-h", "--help")
def stats():
    pass

@stats.command("collect-all")
@click.option(
    "--years",
    type=int,
    default=1,
    help="Fetch GA data for last N years. Default 1",
)
def collect_all(years):
    """Fetch data for last n years"""
    for date in _get_dates_for_last_n_years(years):
        _collect(date)

    _fill_empty_values_for_packages()

def _get_dates_for_last_n_years(years) -> list[str]:
    """Return a list of dates in %Y-%m format"""
    current_year: int = dt.today().year
    current_month: int = dt.today().month

    dates: list[str] = []

    try:
        for year in range(current_year - years + 1, current_year + 1):
            for month in range(1, 13):
                dates.append(f"{year}-{month:02}")

                if (month == current_month) and (year == current_year):
                    raise PresentDate
    except PresentDate:
        pass

    return dates

def _fill_empty_values_for_packages():
    user: dict[str, Any] = tk.get_action("get_site_user")({"ignore_auth": True}, {})
    context = {"user": user["name"]}

    current_year: int = dt.today().year
    current_month: int = dt.today().month
    stats = _get_or_create_overall_stats(context)

    for pkg_id, stats_data in stats.items():
        package = model.Package.get(pkg_id)

        creation_year: int = package.metadata_created.year
        creation_month: int = package.metadata_created.month

        try:
            for year in range(creation_year, current_year + 1):
                for month in range(1, 13):
                    if year == creation_year and month < creation_month:
                        continue

                    stats_data.setdefault(
                        f"{year}-{month:02}", {"views": 0, "downloads": 0}
                    )

                    if (month == current_month) and (year == current_year):
                        raise PresentDate
        except PresentDate:
            pass

    tk.get_action("flakes_flake_override")(
        context,
        {"data": stats, "name": DGA_STATS},
    )

@stats.command("collect")
def collect():
    """Fetch fresh stats from Google Analytics"""
    _collect(dt.today().strftime("%Y-%m"))
    _fill_empty_values_for_packages()

def _collect(date):
    user: dict[str, Any] = tk.get_action("get_site_user")({"ignore_auth": True}, {})
    context = {"user": user["name"]}

    stats: dict[str, dict[str, int]] = get_stats(date)

    monthly_stats = tk.get_action("flakes_flake_override")(
        context,
        {"data": stats, "name": f"{DGA_STATS}:{date}"},
    )

    update_overall_stats(context, monthly_stats, date)

def get_stats(date: str) -> dict[str, dict[str, int]]:
    """Return parsed stats data from Google Analytics"""
    stats = {}

    parse_views_report(stats, get_dataset_views(date))
    parse_downloads_report(stats, get_resource_downloads(date))

    _fill_stats_with_zeros_if_empty(stats)

    return stats

def parse_views_report(stats, ga_data):
    """Parse dataset views GA report"""
    for row in ga_data["rows"]:
        match = RE_PKG.search(row[PAGE_PATH])

        if not match:
            continue

        pkg_id: str = match.group("id")
        pkg = model.Package.get(pkg_id)

        if not pkg:
            continue

        stats.setdefault(pkg.id, {})
        stats[pkg.id].setdefault("views", 0)
        stats[pkg.id]["views"] += int(row[PAGE_VIEWS])

def parse_downloads_report(stats, ga_data):
    """Parse resource downloads GA report"""
    for row in ga_data["rows"]:
        match = RE_RES.search(row[PAGE_PATH])

        if not match:
            continue

        res_id: str = match.group("res_id")
        pkg_id: str = match.group("pkg_id")

        res = model.Resource.get(res_id)
        pkg = model.Package.get(pkg_id)

        if not res or not pkg:
            continue

        stats.setdefault(pkg.id, {})
        stats[pkg.id].setdefault("downloads", 0)
        stats[pkg.id]["downloads"] += int(row[RES_DOWNLOADS])

def get_dataset_views(date) -> GaData:
    """Return dataset views GA report"""
    data_dict = {
        "dimensions": "ga:pagePath",
        "metrics": "ga:pageviews",
    }

    return _get_stats_data(date, data_dict)

def get_resource_downloads(date) -> GaData:
    """Return resource downloads GA report"""
    data_dict = {
        "dimensions": "ga:pagePath,ga:eventCategory,ga:eventAction",
        "metrics": "ga:totalEvents",
        "category": "Resource",
        "action": "Download",
    }

    return _get_stats_data(date, data_dict)

def _get_stats_data(date, data_dict) -> GaData:
    date_month_start: str = str(dt.strptime(date, "%Y-%m").date())
    year, month = date.split("-")
    last_day: int = calendar.monthrange(int(year), int(month))[1]
    date_last_day: str = str(dt.strptime(f"{date}-{last_day:02}", "%Y-%m-%d").date())

    data_dict["start_date"] = date_month_start
    data_dict["end_date"] = date_last_day

    return tk.get_action("googleanalytics_event_report")(
        {"ignore_auth": True}, data_dict
    )

def update_overall_stats(context, stats: dict[str, Any], date: str):
    """Update overall stats from month stats"""
    overall_stats: dict[str, Any] = _get_or_create_overall_stats(context)

    for pkg_id, data in stats["data"].items():
        overall_stats.setdefault(pkg_id, {})
        overall_stats[pkg_id][date] = data

    tk.get_action("flakes_flake_override")(
        context,
        {"data": overall_stats, "name": DGA_STATS},
    )

def _get_or_create_overall_stats(context) -> dict[str, Any]:
    """Return overall GA stats"""
    try:
        stats = tk.get_action("flakes_flake_lookup")(
            context,
            {"name": DGA_STATS},
        )
    except tk.ObjectNotFound:
        stats = tk.get_action("flakes_flake_create")(
            context,
            {"data": {}, "name": DGA_STATS},
        )

    return stats["data"]

def _fill_stats_with_zeros_if_empty(stats):
    for stat in stats.values():
        stat.setdefault("views", 0)
        stat.setdefault("downloads", 0)


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

import matplotlib.pyplot as plt
from matplotlib import style
from matplotlib.widgets import Slider
import numpy
from matplotlib.widgets import CheckButtons

style.use("ggplot")
class GraficaBarras:
    def __init__(self, id, lista_hora, lista_thda, lista_armonico0, lista_armonico1, lista_armonico2, lista_armonico3, lista_armonico4, lista_armonico5, lista_armonico6, lista_armonico7, lista_armonico8, lista_armonico9, lista_armonico10, lista_armonico11 ):
        self.id = id
        self.lista_hora = lista_hora
        self.lista_thda = lista_thda
        self.lista_armonico0 = lista_armonico0
        self.lista_armonico1 = lista_armonico1
        self.lista_armonico2 = lista_armonico2
        self.lista_armonico3 = lista_armonico3
        self.lista_armonico4 = lista_armonico4
        self.lista_armonico5 = lista_armonico5
        self.lista_armonico6 = lista_armonico6
        self.lista_armonico7 = lista_armonico7
        self.lista_armonico8 = lista_armonico8
        self.lista_armonico9 = lista_armonico9
        self.lista_armonico10 = lista_armonico10
        self.lista_armonico11 = lista_armonico11

        self.txt_thd = []
        self.txt_corriente_fundamental = []
        self.txt_armonico1 = []
        self.txt_armonico2 = []
        self.txt_armonico3 = []
        self.txt_armonico4 = []
        self.txt_armonico5 = []
        self.txt_armonico6 = []
        self.txt_armonico7 = []
        self.txt_armonico8 = []
        self.txt_armonico9 = []
        self.txt_armonico10 = []
        self.txt_armonico11 = []

        self.letra_armonico = ['A', 'V']

        self.fig,self.ax=plt.subplots()

        self.N=2
        self.slider(self.N)

    def slider(self,N):
        barpos = plt.axes([0.18, 0.01, 0.70, 0.03], facecolor="gray")
        slider = Slider(barpos, '', 0, len(self.lista_hora)-N, valinit=0)
        slider.on_changed(self.bar)

        rax = plt.axes([0.01, 0.30, 0.12, 0.65])
        check = CheckButtons(rax, ('C. Fund.', 'Arm. 1', 'Arm. 2', 'Arm. 3', 'Arm. 4',
        'Arm. 5', 'Arm. 6', 'Arm. 7', 'Arm. 8', 'Arm. 9', 'Arm. 10', 'Arm. 11'), (True, True,
        True, True, True, True, True, True, True, True, True, True))
        check.get_status()

        check.on_clicked(self.func)
        self.bar(0)
        plt.show()

    def bar(self,pos):

        pos = int(pos)
        self.ax.clear()
        if pos+self.N > len(self.lista_hora):
            n=len(self.lista_hora)-pos
        else:
            n=self.N
        X=self.lista_hora[pos:pos+n]
        Y=self.lista_thda[pos:pos+n]
        self.Y0=self.lista_armonico0[pos:pos+n]
        Y1=self.lista_armonico1[pos:pos+n]
        Y2=self.lista_armonico2[pos:pos+n]
        Y3=self.lista_armonico3[pos:pos+n]
        Y4=self.lista_armonico4[pos:pos+n]
        Y5=self.lista_armonico5[pos:pos+n]
        Y6=self.lista_armonico6[pos:pos+n]
        Y7=self.lista_armonico7[pos:pos+n]
        Y8=self.lista_armonico8[pos:pos+n]
        Y9=self.lista_armonico9[pos:pos+n]
        Y10=self.lista_armonico10[pos:pos+n]
        Y11=self.lista_armonico11[pos:pos+n]

        ind = numpy.arange(len(X))
        self.b_corriente_fundamental = self.ax.bar(X,self.Y0,width=0.06,visible=True,align='edge', data=None, label='C. Fund.',color='#94FF3F',ecolor='black')
        self.b_armonico1 = self.ax.bar(ind+0.06,Y1,width=0.06,visible=True,align='edge',label='Arm. 1',color='#FF4618',ecolor='black')
        self.b_armonico2 = self.ax.bar(ind+0.12,Y2,width=0.06,visible=True,align='edge',label='Arm. 2',color='#1DC0CC',ecolor='black')
        self.b_armonico3 = self.ax.bar(ind+0.18,Y3,width=0.06,visible=True,align='edge',label='Arm. 3',color='#77CC32',ecolor='black')
        self.b_armonico4 = self.ax.bar(ind+0.24,Y4,width=0.06,visible=True,align='edge',label='Arm. 4',color='#CC3813',ecolor='black')
        self.b_armonico5 = self.ax.bar(ind+0.3,Y5,width=0.06,visible=True,align='edge',label='Arm. 5',color='#24F0FF',ecolor='black')
        self.b_armonico6 = self.ax.bar(ind+0.36,Y6,width=0.06,visible=True,align='edge',label='Arm. 6',color='#BFFF8E',ecolor='black')
        self.b_armonico7 = self.ax.bar(ind+0.42,Y7,width=0.06,visible=True,align='edge',label='Arm. 7',color='#FF8465',ecolor='black')
        self.b_armonico8 = self.ax.bar(ind+0.48,Y8,width=0.06,visible=True,align='edge',label='Arm. 8',color='#387B7F',ecolor='black')
        self.b_armonico9 = self.ax.bar(ind+0.54,Y9,width=0.06,visible=True,align='edge',label='Arm. 9',color='#4A7F1F',ecolor='black')
        self.b_armonico10 = self.ax.bar(ind+0.60,Y10,width=0.06,visible=True,align='edge',label='Arm. 10',color='#FF7A7A',ecolor='black')
        self.b_armonico11 = self.ax.bar(ind+0.66,Y11,width=0.06,visible=True,align='edge',label='Arm. 11',color='#3FFF1F',ecolor='black')

        plt.subplots_adjust(left=0.15, right=0.98, top=0.95, bottom=0.25)

        thd = []
        for i,txt in enumerate(Y):
            thd.append(txt)

        for i,txt in enumerate(self.Y0):
            self.txt_corriente_fundamental.append(self.ax.annotate(txt, (X[i],self.Y0[i]), visible=True, ha='center', va='bottom', fontsize=8, rotation=45, color='black'))
        for i,txt in enumerate(Y1):
            self.txt_armonico1.append(self.ax.annotate(txt, (ind[i]+0.06,Y1[i]), visible=True, ha='center', va='bottom', fontsize=8, rotation=45, color='black'))
        for i,txt in enumerate(Y2):
            self.txt_armonico2.append(self.ax.annotate(txt, (ind[i]+0.12,Y2[i]), visible=True, ha='center', va='bottom', fontsize=8, rotation=45, color='black'))
        for i,txt in enumerate(Y3):
            self.txt_armonico3.append(self.ax.annotate(txt, (ind[i]+0.18,Y3[i]), visible=True, ha='center', va='bottom', fontsize=8, rotation=45, color='black'))
        for i,txt in enumerate(Y4):
            self.txt_armonico4.append(self.ax.annotate(txt, (ind[i]+0.24,Y4[i]), visible=True, ha='center', va='bottom', fontsize=8, rotation=45, color='black'))
        for i,txt in enumerate(Y5):
            self.txt_armonico5.append(self.ax.annotate(txt, (ind[i]+0.3,Y5[i]),visible=True, ha='center', va='bottom', fontsize=8, rotation=45, color='black'))
        for i,txt in enumerate(Y6):
            self.txt_armonico6.append(self.ax.annotate(txt, (ind[i]+0.36,Y6[i]), visible=True, ha='center', va='bottom', fontsize=8, rotation=45, color='black'))
        for i,txt in enumerate(Y7):
            self.txt_armonico7.append(self.ax.annotate(txt, (ind[i]+0.42,Y7[i]), visible=True, ha='center', va='bottom', fontsize=8, rotation=45, color='black'))
        for i,txt in enumerate(Y8):
            self.txt_armonico8.append(self.ax.annotate(txt, (ind[i]+0.48,Y8[i]), visible=True, ha='center', va='bottom', fontsize=8, rotation=45, color='black'))
        for i,txt in enumerate(Y9):
            self.txt_armonico9.append(self.ax.annotate(txt, (ind[i]+0.54,Y9[i]), visible=True, ha='center', va='bottom', fontsize=8, rotation=45, color='black'))
        for i,txt in enumerate(Y10):
            self.txt_armonico10.append(self.ax.annotate(txt, (ind[i]+0.60,Y10[i]), visible=True, ha='center', va='bottom', fontsize=8, rotation=45, color='black'))
        for i,txt in enumerate(Y11):
            self.txt_armonico11.append(self.ax.annotate(txt, (ind[i]+0.66,Y11[i]), visible=True, ha='center', va='bottom', fontsize=8, rotation=45, color='black'))
       # miercoles 10 Am
        armonico = ["Corriente","Tensión"]
        self.ax.set_title('Armónicos de %s'%armonico[self.id - 1] )

        plt.text(x = 2, y = 1, s = 'THD%s: %s'%(self.letra_armonico[self.id - 1],thd[0]),
         fontsize = 8,
            bbox={'facecolor':'white', 'alpha':0.99, 'pad':5})
        plt.text(x = 6.7, y = 1, s = 'THD%s: %s'%(self.letra_armonico[self.id - 1],thd[1]), fontsize = 8,
            bbox={'facecolor':'white', 'alpha':0.99, 'pad':5})
        self.ax.xaxis.set_ticks(X)
        self.ax.yaxis.set_ticks([])
        #box = dict(facecolor='green', pad=5, alpha=0.2)
        #self.ax.set_xlabel('Tiempo(Minuto)', bbox=box)
        self.ax.legend(loc='upper center', bbox_to_anchor=(0.45, -0.07),
          fancybox=True, shadow=True, ncol=5)

    def func(self, label):
        for i in range(self.N):
            if label == 'C. Fund.':
                self.b_corriente_fundamental[i].set_visible(not self.b_corriente_fundamental[i].get_visible())

            elif label == 'Arm. 1':
                self.b_armonico1[i].set_visible(not self.b_armonico1[i].get_visible())
                self.txt_armonico1[i].set_visible(not self.txt_armonico1[i].get_visible())

            elif label == 'Arm. 2':
                self.b_armonico2[i].set_visible(not self.b_armonico2[i].get_visible())
                self.txt_armonico2[i].set_visible(not self.txt_armonico2[i].get_visible())

            elif label == 'Arm. 3':
                self.b_armonico3[i].set_visible(not self.b_armonico3[i].get_visible())
                self.txt_armonico3[i].set_visible(not self.txt_armonico3[i].get_visible())

            elif label == 'Arm. 4':
                self.b_armonico4[i].set_visible(not self.b_armonico4[i].get_visible())
                self.txt_armonico4[i].set_visible(not self.txt_armonico4[i].get_visible())

            elif label == 'Arm. 5':
                self.b_armonico5[i].set_visible(not self.b_armonico5[i].get_visible())
                self.txt_armonico5[i].set_visible(not self.txt_armonico5[i].get_visible())

            elif label == 'Arm. 6':
                self.b_armonico6[i].set_visible(not self.b_armonico6[i].get_visible())
                self.txt_armonico6[i].set_visible(not self.txt_armonico6[i].get_visible())

            elif label == 'Arm. 7':
                self.b_armonico7[i].set_visible(not self.b_armonico7[i].get_visible())
                self.txt_armonico7[i].set_visible(not self.txt_armonico7[i].get_visible())

            elif label == 'Arm. 8':
                self.b_armonico8[i].set_visible(not self.b_armonico8[i].get_visible())
                self.txt_armonico8[i].set_visible(not self.txt_armonico8[i].get_visible())

            elif label == 'Arm. 9':
                self.b_armonico9[i].set_visible(not self.b_armonico9[i].get_visible())
                self.txt_armonico9[i].set_visible(not self.txt_armonico9[i].get_visible())

            elif label == 'Arm. 10':
                self.b_armonico10[i].set_visible(not self.b_armonico10[i].get_visible())
                self.txt_armonico10[i].set_visible(not self.txt_armonico10[i].get_visible())

            elif label == 'Arm. 11':
                self.b_armonico11[i].set_visible(not self.b_armonico11[i].get_visible())
                self.txt_armonico11[i].set_visible(not self.txt_armonico11[i].get_visible())

            if label == 'C. Fund.':
                for y in range(len(self.Y0)):
                    self.txt_corriente_fundamental[i].set_visible(not self.txt_corriente_fundamental[y].get_visible())

        plt.draw()
    '''def txtBarra(self, X, Y):
                    while True:
                        for i,txt in enumerate(Y):
                            txt = self.ax.annotate(txt, (X[i],Y[i]))
                            return txt'''


DATA_READ_ERR = 1001
ABSCISSA_READ_ERR = 1002
SIGMA_READ_ERR = 1003
SAMPLER_RUN_ERR = 1004
PRIOR_INIT_ERR = 1005
LIKE_INIT_ERR = 1006
POST_OUTPUT_ERR = 1007
POST_PROCESS_ERR = 1008
DATA_LENGTH_ERR = 1009

errormessages = {}
errormessages[DATA_READ_ERR] = "There was a problem reading in the data."
errormessages[ABSCISSA_READ_ERR] = "There was a problem reading in the abscissa variable file."
errormessages[SIGMA_READ_ERR] = "There was a problem reading in the data standard deviation file."
errormessages[SAMPLER_RUN_ERR] = "There was a problem running the sampler."
errormessages[PRIOR_INIT_ERR] = "There was a problem initialising the prior values."
errormessages[LIKE_INIT_ERR] = "There was a problem initialising the sampler."
errormessages[POST_OUTPUT_ERR] = "There was a problem outputing the posterior file."
errormessages[POST_PROCESS_ERR] = "There was a problem running the post-processing page."
errormessages[DATA_LENGTH_ERR] = "There are inconsistent lengths between data, abscissa, and/or sigma lengths."


from django.contrib import admin
from order_placing.models import *

class ReqAdmin(admin.ModelAdmin):
	list_display = ('requester','accepter','order_time','home_location','shop_location')

	filter_horizontal = ()
	list_filter = ()
	fieldsets = ()

admin.site.register(Request,ReqAdmin)


exec(open("Modified_data/cannon_aero.dr").read())

dyn_integloop.getIntegrator(trick.Runge_Kutta_4, 6)

trick.mc_set_enabled(1)
#trick.mc_set_dry_run(1)
trick.mc_set_num_runs(50)
#trick.mc_add_range(5)
# Inline method below

# If configure trick build with -std=c++0x or -std=c++11 if available,
# can use trick.MonteVarRandom.TRICK_DEFAULT_ENGINE, or other selections.
# to use C++11 <random> pseudo-random number generators

var0 = trick.MonteVarRandom("dyn.baseball.time_to_fire_jet_1",trick.MonteVarRandom.GAUSSIAN,"s",trick.MonteVarRandom.NO_ENGINE) # NO_ENGINE is default
var0.set_seed(1)
var0.set_sigma(0.6667)
var0.set_mu(4.0)
var0.set_min(-4.0)
var0.set_max(4.0)
var0.set_min_is_relative(True) # default is True
var0.set_max_is_relative(True) # default is True
var0.set_sigma_range(0) # 1 is default, so usually set to zero to turn off the algorithm

var1 = trick.MonteVarRandom("dyn.baseball.time_to_fire_jet_2",trick.MonteVarRandom.GAUSSIAN)
var1.set_seed(2)
var1.set_sigma(0.6667)
var1.set_mu(4.0)
var1.set_min(-4.0)
var1.set_max(4.0)
var1.set_sigma_range(0)

var2 = trick.MonteVarRandom("dyn.baseball.time_to_fire_jet_3",trick.MonteVarRandom.GAUSSIAN,"s")
var2.set_seed(3)
var2.set_sigma(0.6667)
var2.set_mu(4.0)
var2.set_min(-4.0)
var2.set_max(4.0)
var2.set_sigma_range(0)

var3 = trick.MonteVarRandom("dyn.baseball.time_to_fire_jet_4",trick.MonteVarRandom.GAUSSIAN)
var3.set_seed(4)
var3.set_sigma(0.6667)
var3.set_mu(4.0)
var3.set_min(-4.0)
var3.set_max(4.0)
var3.set_sigma_range(0)

trick_mc.mc.add_variable(var0)
trick_mc.mc.add_variable(var1)
trick_mc.mc.add_variable(var2)
trick_mc.mc.add_variable(var3)

trick.mc_set_timeout(2)

dyn.baseball.pos[0] = 0.0
dyn.baseball.pos[1] = 0.0
dyn.baseball.pos[2] = 0.0

dyn.baseball.vel[0] = 43.30
dyn.baseball.vel[1] = 0.0
dyn.baseball.vel[2] = 25.0

dyn.baseball.theta  = trick.attach_units("d" , -90.0)
dyn.baseball.phi    = trick.attach_units("d" , 1.0)
dyn.baseball.omega0 = trick.attach_units("rev/s" , 30.0)

trick.stop(10.0)


from odoo.addons.crm.tests.test_crm_lead_merge import TestLeadMergeCommon
from odoo.addons.event_crm.tests.common import TestEventCrmCommon
from odoo.tests.common import tagged, users

@tagged('lead_manage')
class TestLeadCrmMerge(TestLeadMergeCommon, TestEventCrmCommon):

    @users('user_sales_manager')
    def test_merge_method_dependencies(self):
        """ Test if dependences for leads are not lost while merging leads. In
        this test leads are ordered as

        lead_w_contact -----------lead---seq=30
        lead_w_email -------------lead---seq=3
        lead_1 -------------------lead---seq=1
        lead_w_partner_company ---lead---seq=1----------------registrations
        lead_w_partner -----------lead---seq=False
        """
        TestLeadMergeCommon.merge_fields.append('registration_ids')

        registration = self.env['event.registration'].sudo().create({
            'partner_id': self.event_customer.id,
            'event_id': self.event_0.id,
            'lead_ids': [(4, self.lead_w_partner_company.id)],
        })
        self.assertEqual(self.lead_w_partner_company.registration_ids, registration)

        leads = self.env['crm.lead'].browse(self.leads.ids)._sort_by_confidence_level(reverse=True)
        with self.assertLeadMerged(self.lead_w_contact, leads,
                                   name=self.lead_w_contact.name,
                                   registration_ids=registration
                                   ):
            leads._merge_opportunity(auto_unlink=False, max_length=None)


import math
from typing import Tuple

import torch
from torch import nn

class DotProductCritic(nn.Module):
    """
    A critic 𝑈 that takes two quantities, 𝑥 and 𝑦, as inputs and returns a real number.

    In the context of BED, we want to take as input some observations 𝑦 and some quantity of interest 𝑥
    (which we wish to gain information about). These usually live in different spaces, so we first encode
    them (e.g. with two MLPs) so they are in the same space and of the same dimension.

    A dot product critic is defined as the dot product between the encodings:
        𝑈(𝑥, 𝑦) = 𝐸₁(𝑥)ᵀ𝐸₂(𝑦)
    Optionally, the dot product can be scaled by sqrt(encoding_dimension), i.e.
         𝑈(𝑥, 𝑦) = 𝐸₁(𝑥)ᵀ𝐸₂(𝑦) / sqrt(encoding_dimension)
    """

    def __init__(
        self, x_encoder: nn.Module, y_encoder: nn.Module, scale: bool = True
    ) -> None:
        super().__init__()
        self.x_encoder = x_encoder
        self.y_encoder = y_encoder
        self.scale = scale

    def forward(
        self, x: torch.Tensor, y: torch.Tensor
    ) -> Tuple[torch.Tensor, torch.Tensor]:
        """
        Computes 𝑈(x_i, y_j) for i, j = 1,..., batch_size
        Args:
            x: first input, expected shape [batch_size, dim(x)])
            y: second input, expected shape [batch_size, dim(y)])
        Returns:
            A tuple of two torch matrices:
                joint scores, containing 𝑈(x_i, y_i) on the diagonal.
                product scores, containing 𝑈(x_i, y_j) and 0 on the diagonal.
        """
        x_encodings = self.x_encoder(x)  # [batch_size, encoding_dim]
        y_encodings = self.y_encoder(y)  # [batch_size, encoding_dim]
        assert (
            x_encodings.shape == y_encodings.shape
        ), "Encoding and batch dimensions of x and y must be the same."

        factor = 1 / math.sqrt(y_encodings.shape[-1]) if self.scale else 1.0
        score_matrix = torch.matmul(x_encodings, y_encodings.transpose(-1, -2)) * factor
        mask = torch.eye(y_encodings.shape[0], device=y_encodings.device)
        joint_scores = score_matrix * mask
        marginal_product_scores = score_matrix * (1 - mask)

        return joint_scores, marginal_product_scores


import numpy as np
import scipy
import scipy.sparse
import scipy.sparse.linalg
import matplotlib.pyplot as plt

class NumericalSolver():
    def __init__(self):
        self.eps = 1    # Multiplier unique to specific biological systems
        self.h = 0.05   # Spacial step
        self.alpha = 3.0    # Exponent
        self.k = 0.4 * self.h ** 2 / self.eps   # Time step
        self.Tf = 42.0  # Total time frame
        self.x = np.arange(-15, 15, self.h) # 1D spacial coordinate
        self.N = len(self.x)    # Size of x-space
        self.bc = np.concatenate(([1], np.zeros(self.N-1)))/self.h**2   # Application of the Neumann boundary conditions
        self.x_anal = np.linspace(-15, 15, 10)  # 1D spacial coordinate for the scatter plot of the analytical solution

    def Analytical_Solution_Special(self, x, t):
        """
        Function that generates the analytical solution for the specialised Fischer Equation
        :param alpha: Exponent in the specialised Fischer equation
        :param x: x space
        :param t: time
        :return: Analytical solution of the specialised Fischer equation with respect to x at time t.
        """
        return (-(1/2)*np.tanh(self.alpha/(2*(2*self.alpha+4)**(1/2))*(x-((self.alpha+4)*t)/((2*self.alpha + 4)**(1/2))))+1/2)**(2/self.alpha)

    def construct_laplace_matrix_1d(self):
        """
        Function generates an NxN 1D laplace matrix
        :param N: Number of iterations
        :param h: Spacial step
        :return: N x N 1D laplace matrix
        """
        e = np.ones(self.N)
        diagonals = [e, -2*e, e]
        offsets = [-1,0,1]
        L = scipy.sparse.spdiags(diagonals, offsets, self.N, self.N) / self.h**2
        return L

    def get_sols(self, u, L, out):
        """
        :return: Generates an array of numerical solutions to the specialised Fischer equation
        """
        for i in range(int(self.Tf / self.k)):
            u_new = u + self.k*(self.eps * (L * u + self.bc) + u*(1 - u**self.alpha))
            out.append([u_new])
            u[:] = u_new
        return u, out

    def solve_turing(self):
        """
        :return: Solves the specialised Fischer Equation given the default parameters
        """
        L = NumericalSolver().construct_laplace_matrix_1d()
        u = NumericalSolver().Analytical_Solution_Special(self.x, 0)
        out = []

        u, out = NumericalSolver().get_sols(u, L, out)
        out.append([u])
        return u, self.x, out

    def plot_fig(self):
        """
        :return: Generates plot comparing the numerical and analytical solutions to the specialised Fischer equation, comparing solutions at t=0, t=2 and t=4.
        Second plot compares the squared error between the analytical and numerical solution at t=0, t=2 and t=4 as a function of x.
        """
        u, x, out = NumericalSolver().solve_turing()

        #Analytical solution at t=0, t=2 and t=4 respectively
        u_analstart = NumericalSolver().Analytical_Solution_Special(self.x_anal, 0)
        u_analhalf = NumericalSolver().Analytical_Solution_Special(self.x_anal, 2)
        u_analend = NumericalSolver().Analytical_Solution_Special(self.x_anal, 4)

        plt.figure()
        plt.plot(x, out[0][0], label='Numerical t=0', color='blue')
        plt.plot(x, out[int(len(out) / self.Tf) * 2][0], label='Numerical t=2', color='green')
        plt.plot(x, out[int(len(out) / self.Tf) * 4][0], label='Numerical t=4', color='red')
        plt.scatter(self.x_anal, u_analstart, label='Analytical t=0', color='blue')
        plt.scatter(self.x_anal, u_analhalf, label='Analytical t=2', color='green')
        plt.scatter(self.x_anal, u_analend, label='Analytical t=4', color='red')
        plt.xlabel('x')
        plt.ylabel('Solution')
        plt.legend()
        plt.show()

        u_anal0_error = NumericalSolver().Analytical_Solution_Special(x, 0)
        u_anal2_error = NumericalSolver().Analytical_Solution_Special(x, 2)
        u_anal4_error = NumericalSolver().Analytical_Solution_Special(x, 4)

        # Initialise arrays
        error0 = []
        error2 = []
        error4 = []

        for i in range(len(x)):
            error0.append((u_anal0_error[i] - out[0][0][i]) ** 2)
            error2.append((u_anal2_error[i] - out[int(len(out) / self.Tf) * 2][0][i]) ** 2)
            error4.append((u_anal4_error[i] - out[int(len(out) / self.Tf) * 4][0][i]) ** 2)

        # plt.figure()
        plt.plot(x, error0, label='Error t=0', color='blue')
        plt.plot(x, error2, label='Error t=2', color='green')
        plt.plot(x, error4, label='Error t=4', color='red')
        plt.xlabel('x')
        plt.ylabel('Error')
        plt.show()

# a = NumericalSolver()
# a.plot_fig()


import numpy as np
import os
import glob
import tensorflow as tf
import argparse
import random
import sklearn.metrics
from tensorflow.keras.preprocessing import image
import matplotlib.cm as cm

import ecdl

import csv
import io
import json

import tensorflow_addons as tfa
import matplotlib.pyplot as plt

gpus = tf.config.experimental.list_physical_devices('GPU')
if gpus:
    try:
        # Currently, memory growth needs to be the same across GPUs
        for gpu in gpus:
            tf.config.experimental.set_memory_growth(gpu, True)
        logical_gpus = tf.config.experimental.list_logical_devices('GPU')
        print(len(gpus), "Physical GPUs,", len(logical_gpus), "Logical GPUs")
    except RuntimeError as e:
        # Memory growth must be set before GPUs have been initialized
        print(e)

class myargs():
    def __init__(self):
        self.exp_id = 'efficientnetb1_round3'
        self.test_data = 'first'
        self.load_type = 'weight'

args = myargs()

base_exp_path = '/workspaces/ecdl/experiments'
exp_path = os.path.join(base_exp_path, args.exp_id)
args_path = os.path.join(exp_path, 'hyperparameters.json')
with open(args_path) as json_file:
    data_json = json.load(json_file)

model = tf.keras.models.load_model(exp_path)

if args.load_type == 'weight':
    best_metric = 0.0
    chosen_index = 0
    all_weights = glob.glob(os.path.join(exp_path,'*.hdf5'))
    metric_list = [float(weight_file.split('-')[-1].split('.hdf5')[0]) for weight_file in all_weights]
    for index,metric_value in enumerate(metric_list):
        if best_metric < metric_value:
            best_metric = metric_value
            chosen_index = index
    model.load_weights(all_weights[chosen_index])

model.compile(
    optimizer='adam',
    loss='categorical_crossentropy',
    metrics=[
        'accuracy',
        tf.keras.metrics.AUC(
        num_thresholds=200, curve='ROC', summation_method='interpolation', name=None,
        dtype=None, thresholds=None, multi_label=False, label_weights=None)
        ])

base_image_generator = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255)

def produce_model_inputs(img_path, data_json):
    # Read the image and resize it
    img = image.load_img(img_path, target_size=(data_json['image_dims'], data_json['image_dims']))
    # Convert it to a Numpy array with target shape.
    image_array = image.img_to_array(img)
    normalised_image = image_array/255.

    # model_input has shape [1, 256, 256, 3]
    model_input = tf.expand_dims(normalised_image,axis=0)
    return model_input

def predict_image(model_input, model):
    prediction = model(model_input)

    if tf.argmax(prediction,axis=-1)[0] == 0:
        result = 'Abnormal'
    elif tf.argmax(prediction,axis=-1)[0] == 1:
        result = 'Normal'

    return prediction, result

def make_gradcam_heatmap(
    img_array,
    model,
    activation_layer,
):

    with tf.GradientTape() as tape:
        last_conv_layer = model.get_layer(activation_layer)
        iterate = tf.keras.models.Model([model.inputs], [model.output, last_conv_layer.output])
        model_out, last_conv_layer = iterate(img_array)
        class_out = model_out[:, np.argmax(model_out[0])]
        grads = tape.gradient(class_out, last_conv_layer)
        pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))

    last_conv_layer_output = last_conv_layer.numpy()[0]
    pooled_grads = pooled_grads.numpy()
    for i in range(pooled_grads.shape[-1]):
        last_conv_layer_output[:, :, i] *= pooled_grads[i]

    # The channel-wise mean of the resulting feature lbow_data/SABINE 114 size test set/004_Normal022.jpgap
    # is our heatmap of class activation
    heatmap = np.mean(last_conv_layer_output, axis=-1)

    # For visualization purpose, we will also normalize the heatmap between 0 & 1
    heatmap = np.maximum(heatmap, 0) / np.max(heatmap)
    return heatmap

def overlay_heatmap(sample_image_path, heatmap, data_json):
    # We load the original image
    img = tf.keras.preprocessing.image.load_img(sample_image_path, target_size=(data_json['image_dims'], data_json['image_dims']))
    img = tf.keras.preprocessing.image.img_to_array(img)

    # We rescale heatmap to a range 0-255
    heatmap = np.uint8(255 * heatmap)

    # We use jet colormap to colorize heatmap
    jet = cm.get_cmap("jet")

    # We use RGB values of the colormap
    jet_colors = jet(np.arange(256))[:, :3]
    jet_heatmap = jet_colors[heatmap]

    # We create an image with RGB colorized heatmap
    jet_heatmap = tf.keras.preprocessing.image.array_to_img(jet_heatmap)
    jet_heatmap = jet_heatmap.resize((img.shape[1], img.shape[0]))
    jet_heatmap = tf.keras.preprocessing.image.img_to_array(jet_heatmap)

    # Superimpose the heatmap on original image
    superimposed_img = jet_heatmap * 0.4 + img
    superimposed_img = tf.keras.preprocessing.image.array_to_img(superimposed_img)

    model_input = produce_model_inputs(sample_image_path, data_json)
    prediction, result = predict_image(model_input,model)

    plt.figure(figsize=[20,20])
    plt.imshow(superimposed_img)
    plt.xticks([])
    plt.yticks([])
    plt.xlabel(f'Score: {prediction} \nAnswer: {os.path.basename(sample_image_path)}\nPrediction: {result}');

# # sample_image_path = 'elbow_data/SABINE 114 size test set/001_Normal011.jpg'
# # sample_image_path = 'elbow_data/SABINE 114 size test set/002_Abnormal001.jpg'
# # sample_image_path = 'elbow_data/SABINE 114 size test set/003_Abnormal009.jpg'
# # sample_image_path = 'sample_images/elbow_broken.jpeg'
# # sample_image_path = 'sample_images/elbow_fracture_1.jpg'
# sample_image_path = '/workspaces/ecdl/sample_images/elbow_fracture_1.jpg'

# img_array = produce_model_inputs(sample_image_path, data_json)

# # Generate class activation heatmap
# heatmap = make_gradcam_heatmap(
#     img_array,
#     model,
#     activation_layer='top_activation',
# )

# overlay_heatmap(sample_image_path, heatmap, data_json)
# plt.savefig('/workspaces/ecdl/heatmaps/sample.jpg')

types = ('*/*.jpg', '*/*.png') # the tuple of file types
all_files = []
for files in types:
    all_files.extend(glob.glob(os.path.join('/workspaces/ecdl/elbow_data/Clean_Data/SecondTestSet', files)))

for image_path in all_files:
    sample_image_path = image_path

    img_array = produce_model_inputs(sample_image_path, data_json)
    heatmap = make_gradcam_heatmap(
        img_array,
        model,
        activation_layer='top_activation',
    )
    overlay_heatmap(sample_image_path, heatmap, data_json)

    class_path = os.path.dirname(sample_image_path)
    test_path = os.path.dirname(class_path)

    base_name = os.path.basename(sample_image_path)
    real_class = os.path.basename(class_path)
    test_name = os.path.basename(test_path)
    if test_name == 'ActualTestSet':
        saving_base_dir = '/workspaces/ecdl/heatmaps_112'
    elif test_name == 'SecondTestSet':
        saving_base_dir = '/workspaces/ecdl/heatmaps_100'

    if real_class == 'Normal':
        saving_directory = os.path.join(saving_base_dir, 'Normal')
    elif real_class == 'Abnormal':
        saving_directory = os.path.join(saving_base_dir, 'Abnormal')

    final_dir = os.path.join(saving_directory, base_name)

    plt.savefig(final_dir[:-3] + 'jpg')


import socket
import select
#from Crypto.Cipher import AES

HEADER_LENGTH = 10

IP = "192.168.1.10"
PORT = 6980

# Create a socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

server_socket.bind((IP, PORT))

# This makes server listen to new connections
server_socket.listen()

sockets_list = [server_socket]

# List of connected clients
clients = {}

print(f'Listening for connections on {IP}:{PORT}...')

def receive_message(client_socket):
    try:

        message_header = client_socket.recv(HEADER_LENGTH)

        if not len(message_header):
            return False

        # Convert header to int value
        message_length = int(message_header.decode('utf-8').strip())

        return {'header': message_header, 'data': client_socket.recv(message_length)}

    except:

        return False

while True:

    read_sockets, _, exception_sockets = select.select(sockets_list, [], sockets_list)

    for notified_socket in read_sockets:

        if notified_socket == server_socket:

            # Accept new connection
            client_socket, client_address = server_socket.accept()

            user = receive_message(client_socket)

            # If False - client disconnected before he sent his name
            if user is False:
                continue

            # Add accepted socket to select.select() list
            sockets_list.append(client_socket)

            # Also save username and username header
            clients[client_socket] = user

            print('Accepted new connection from {}:{}, username: {}'.format(*client_address,
                                                                            user['data'].decode('utf-8')))

        # Else existing socket is sending a message
        else:

            # Receive message
            message = receive_message(notified_socket)

            # If False, client disconnected, cleanup
            if message is False:
                print('Closed connection from: {}'.format(clients[notified_socket]['data'].decode('utf-8')))

                # Remove from list for socket.socket()
                sockets_list.remove(notified_socket)

                # Remove from our list of users
                del clients[notified_socket]

                continue

            user = clients[notified_socket]

            print(f'Received message from {user["data"].decode("utf-8")}: {message["data"].decode("utf-8")}')

            for client_socket in clients:

                if client_socket != notified_socket:
                    client_socket.send(user['header'] + user['data'] + message['header'] + message['data'])

    for notified_socket in exception_sockets:
        sockets_list.remove(notified_socket)

        del clients[notified_socket]

# counter = "H"*16
# key = "H"*32

# def encrypt(message):
# encrypto = AES.new(key, AES.MODE_CTR, counter=lambda: counter)
# return encrypto.encrypt(message)
#
# def decrypt(message):
# decrypto = AES.new(key, AES.MODE_CTR, counter=lambda: counter)
# return decrypto.decrypt(message)


import typing
from dataclasses import dataclass, field
from typing import Literal, Type, Optional
from pathlib import Path
from rich.progress import (
    BarColumn,
    MofNCompleteColumn,
    Progress,
    TextColumn,
    TimeElapsedColumn,
)
from time import time
from PIL import Image

import torch
import torch.distributed as dist
from torch.cuda.amp.grad_scaler import GradScaler
from torch.nn.parallel import DistributedDataParallel as DDP

from nerfstudio.configs import base_config as cfg
from nerfstudio.models.base_model import ModelConfig
from nerfstudio.pipelines.base_pipeline import (
    VanillaPipeline,
    VanillaPipelineConfig,
)

from nerfstudio.data.datamanagers.base_datamanager import (
    DataManager,
    DataManagerConfig,
    VanillaDataManager,
    VanillaDataManagerConfig,
)
from nerfstudio.utils import profiler

from permuto.permutosdf_model import PermutoSDFModel, PermutoSDFModelConfig

import datetime

def map_range_val( input_val, input_start, input_end,  output_start,  output_end):
    # input_clamped=torch.clamp(input_val, input_start, input_end)
    input_clamped=max(input_start, min(input_end, input_val))
    # input_clamped=torch.clamp(input_val, input_start, input_end)
    return output_start + ((output_end - output_start) / (input_end - input_start)) * (input_clamped - input_start)

@dataclass
class PermutoPipelineConfig(VanillaPipelineConfig):
    """Configuration for pipeline instantiation"""

    _target: Type = field(default_factory=lambda: PermutoPipeline)
    """target class to instantiate"""
    datamanager: VanillaDataManagerConfig = VanillaDataManagerConfig()
    """specifies the datamanager config"""
    model: PermutoSDFModelConfig = PermutoSDFModelConfig()
    """specifies the model config"""

class PermutoPipeline(VanillaPipeline):
    def __init__(
        self,
        config: PermutoPipelineConfig,
        device: str,
        test_mode: Literal["test", "val", "inference"] = "val",
        world_size: int = 1,
        local_rank: int = 0,
        grad_scaler: Optional[GradScaler] = None,
    ):
        super(VanillaPipeline, self).__init__()
        self.config = config
        self.test_mode = test_mode

        self.datamanager: VanillaDataManager = config.datamanager.setup(
            device=device,
            test_mode=test_mode,
            world_size=world_size,
            local_rank=local_rank,
        )
        self.datamanager.to(device)

        # TODO(ethan): get rid of scene_bounds from the model
        assert self.datamanager.train_dataset is not None, "Missing input dataset"

        self._model = config.model.setup(
            scene_box=self.datamanager.train_dataset.scene_box,
            num_train_data=len(self.datamanager.train_dataset),
            metadata=self.datamanager.train_dataset.metadata,
            world_size=world_size,
            local_rank=local_rank,
        )
        self.model.to(device)

        self.world_size = world_size
        if world_size > 1:
            self._model = typing.cast(PermutoSDFModel, DDP(self._model, device_ids=[local_rank], find_unused_parameters=True))
            dist.barrier(device_ids=[local_rank])

    @profiler.time_function
    def get_train_loss_dict(self, step: int):
        """This function gets your training loss dict. This will be responsible for
        getting the next batch of data from the DataManager and interfacing with the
        Model class, feeding the data to the model's forward function.

        Args:
            step: current iteration step to update sampler if using DDP (distributed)
        """

        # self.model.field.model_sdf.train(True)
        # self.model.field.model_rgb.train(True)
        # self.model.field.model_bg.train(True)

        ray_bundle, batch = self.datamanager.next_train(step)
        # start_time = datetime.datetime.now()
        model_outputs, loss_outputs = self._model(step, batch, ray_bundle)
        # finish_time = datetime.datetime.now()

        # if step % 1000 == 0:
        #     with open(r'train_time1.txt', 'a') as f:
        #         f.write('step: ' + str(step) + '\n')
        #         f.write('use_time: ' + str(finish_time - start_time) + '\n')

        metrics_dict = self.model.get_metrics_dict(step, model_outputs, batch)

        if self.config.datamanager.camera_optimizer is not None:
            camera_opt_param_group = self.config.datamanager.camera_optimizer.param_group
            if camera_opt_param_group in self.datamanager.get_param_groups():
                # Report the camera optimization metrics
                metrics_dict["camera_opt_translation"] = (
                    self.datamanager.get_param_groups()[camera_opt_param_group][0].data[:, :3].norm()
                )
                metrics_dict["camera_opt_rotation"] = (
                    self.datamanager.get_param_groups()[camera_opt_param_group][0].data[:, 3:].norm()
                )

        loss_dict = self.model.get_loss_dict(step, batch, model_outputs, loss_outputs, metrics_dict=metrics_dict)

        # 9500 10501
        # global_weight_curvature=map_range_val(step, self.model.config.start_reduce_curv, self.model.config.finish_reduce_curv, 1.0, 0.000)
        # if global_weight_curvature > 0.0:
        #     loss_dict["curv_loss"] = loss_dict["curv_loss"] * global_weight_curvature
        # else:
        #     del loss_dict["curv_loss"]

        # if step < self.model.config.start_reduce_curv:
        #     del loss_dict["loss_lipshitz"]

        if step % 1000 == 0:
            with open(r'train_time1.txt', 'a') as f:
                # f.write('loss_dict :' + '\n')
                # f.write(str(loss_dict) + '\n')

                f.write('metrics_dict: ' + str(metrics_dict) + '\n')
                # f.write(str(metrics_dict) + '\n')
                # f.write('\n')
                f.write('\n')
            # print('time time time finish !')
            # now_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            # f.write(str(now_time) + '\n')
            # print(now_time)

        # print('########### metrics_dict ############')
        # print(metrics_dict)

        # print('########### loss_dict ############')
        # print(loss_dict)

        # if (step == self.model.config.start_reduce_curv-1) or (step == self.model.config.start_reduce_curv) or (step == self.model.config.start_reduce_curv+1):
        #     print(step)
        #     print(global_weight_curvature)
        #     print(loss_dict)
        #     print(self.model.config.start_reduce_curv)
        #     print(self.model.config.finish_reduce_curv)
        #     print(self.model)

        return model_outputs, loss_dict, metrics_dict

    @profiler.time_function
    def get_eval_loss_dict(self, step: int):
        """This function gets your evaluation loss dict. It needs to get the data
        from the DataManager and feed it to the model's forward function

        Args:
            step: current iteration step
        """

        self.eval()

        # self.model.field.model_sdf.train(False)
        # self.model.field.model_rgb.train(False)
        # self.model.field.model_bg.train(False)

        ray_bundle, batch = self.datamanager.next_eval(step)
        model_outputs, loss_outputs = self.model(step, batch, ray_bundle)
        metrics_dict = self.model.get_metrics_dict(step, model_outputs, batch)
        loss_dict = self.model.get_loss_dict(step, batch, model_outputs, loss_outputs, metrics_dict=metrics_dict)

        self.train()

        # self.model.field.model_sdf.train(True)
        # self.model.field.model_rgb.train(True)
        # self.model.field.model_bg.train(True)

        return model_outputs, loss_dict, metrics_dict

    @profiler.time_function
    def get_eval_image_metrics_and_images(self, step: int):
        """This function gets your evaluation loss dict. It needs to get the data
        from the DataManager and feed it to the model's forward function

        Args:
            step: current iteration step
        """
        self.eval()
        image_idx, camera_ray_bundle, batch = self.datamanager.next_eval_image(step)
        # outputs = self.model.get_outputs_for_camera_ray_bundle(step, batch, camera_ray_bundle)

        if step > self.model.field.config.nr_iter_sphere_fit:
            outputs = self.model.get_outputs_for_camera_ray_bundle(step, batch, camera_ray_bundle)
            metrics_dict, images_dict = self.model.get_image_metrics_and_images(outputs, batch)
        else:
            metrics_dict = {}
            images_dict = {}
        assert "image_idx" not in metrics_dict
        metrics_dict["image_idx"] = image_idx
        assert "num_rays" not in metrics_dict
        metrics_dict["num_rays"] = len(camera_ray_bundle)
        self.train()
        return metrics_dict, images_dict

    # @profiler.time_function
    # def get_average_eval_image_metrics(
    #     self, step: Optional[int] = None, output_path: Optional[Path] = None, get_std: bool = False
    # ):
    #     """Iterate over all the images in the eval dataset and get the average.

    #     Args:
    #         step: current training step
    #         output_path: optional path to save rendered images to
    #         get_std: Set True if you want to return std with the mean metric.

    #     Returns:
    #         metrics_dict: dictionary of metrics
    #     """
    #     self.eval()
    #     metrics_dict_list = []
    #     assert isinstance(self.datamanager, VanillaDataManager)
    #     num_images = len(self.datamanager.fixed_indices_eval_dataloader)
    #     with Progress(
    #         TextColumn("[progress.description]{task.description}"),
    #         BarColumn(),
    #         TimeElapsedColumn(),
    #         MofNCompleteColumn(),
    #         transient=True,
    #     ) as progress:
    #         task = progress.add_task("[green]Evaluating all eval images...", total=num_images)
    #         for camera_ray_bundle, batch in self.datamanager.fixed_indices_eval_dataloader:
    #             # time this the following line
    #             inner_start = time()
    #             height, width = camera_ray_bundle.shape
    #             num_rays = height * width
    #             outputs = self.model.get_outputs_for_camera_ray_bundle(camera_ray_bundle)
    #             metrics_dict, images_dict = self.model.get_image_metrics_and_images(outputs, batch)

    #             if output_path is not None:
    #                 camera_indices = camera_ray_bundle.camera_indices
    #                 assert camera_indices is not None
    #                 for key, val in images_dict.items():
    #                     Image.fromarray((val * 255).byte().cpu().numpy()).save(
    #                         output_path / "{0:06d}-{1}.jpg".format(int(camera_indices[0, 0, 0]), key)
    #                     )
    #             assert "num_rays_per_sec" not in metrics_dict
    #             metrics_dict["num_rays_per_sec"] = num_rays / (time() - inner_start)
    #             fps_str = "fps"
    #             assert fps_str not in metrics_dict
    #             metrics_dict[fps_str] = metrics_dict["num_rays_per_sec"] / (height * width)
    #             metrics_dict_list.append(metrics_dict)
    #             progress.advance(task)
    #     # average the metrics list
    #     metrics_dict = {}
    #     for key in metrics_dict_list[0].keys():
    #         if get_std:
    #             key_std, key_mean = torch.std_mean(
    #                 torch.tensor([metrics_dict[key] for metrics_dict in metrics_dict_list])
    #             )
    #             metrics_dict[key] = float(key_mean)
    #             metrics_dict[f"{key}_std"] = float(key_std)
    #         else:
    #             metrics_dict[key] = float(
    #                 torch.mean(torch.tensor([metrics_dict[key] for metrics_dict in metrics_dict_list]))
    #             )
    #     self.train()
    #     return metrics_dict


from torchviz import make_dot
import torch
import torch.nn as nn
FLAG=True
class InterFuse(nn.Module):

    def __init__(self,in_dim=2,out_dim=256):
        super(InterFuse,self).__init__()
        self.conv1 = nn.Conv2d(100,256,1,1)
        self.conv2 = nn.Conv2d(256,256,1,1)
        self.conv3 = nn.Conv2d(in_dim,256,1,1)
        self.conv4 = nn.Conv2d(256,out_dim,1,1)
        self.relu = nn.ReLU()
        self.sigmoid = nn.Sigmoid()
        self.bn1 = nn.BatchNorm2d(256)
        self.bn2 = nn.BatchNorm2d(256)

        nn.init.kaiming_normal_(self.conv1.weight,mode='fan_out',nonlinearity='relu')
        nn.init.kaiming_normal_(self.conv2.weight,mode='fan_out',nonlinearity='relu')
        nn.init.kaiming_normal_(self.conv3.weight,mode='fan_out',nonlinearity='relu')
        nn.init.kaiming_normal_(self.conv4.weight,mode='fan_out',nonlinearity='relu')
    def forward(self,x,y):
        x1 = self.conv1(x)
        y  = self.conv3(y)
        if FLAG:
            x2 = self.bn1(x1)
            x2 = self.relu(x2)
            x2 = self.conv2(x2)
            y = self.sigmoid(y)
            out = self.relu(self.bn2(y*x2+x1))
            out = self.conv4(out)
        else:
            out = self.relu(self.bn2(y+x1))
            out = self.conv4(out)
        return out

input1 = torch.randn(1,100,104,104)
input2 = torch.randn(1,2,104,104)
model = InterFuse()
a=make_dot(model(input1,input2), params=dict(model.named_parameters()))
print(a)


import math
from statistics import mean

class CalculateAverageMixin:
    def get_average(self, data):
        return math.floor(mean(data))

class Student(CalculateAverageMixin):
    def __init__(self, name, age, grades=[]):
        self.name = name
        self.age = age
        self.grades = grades
class Employee(CalculateAverageMixin):
    def __init__(self, name, age, daily_working_hours=[]):
        self.name = name
        self.age = age
        self.daily_working_hours = daily_working_hours

student = Student("Maria", 20, [5,6])
teacher = Employee("Gosho", 25, [10,17])
print(student.get_average(student.grades))
print(teacher.get_average(teacher.daily_working_hours))

04. Inheritance/Lab_ 4. Mixin Inheritance.py


from django.conf.urls.static import static
from django.conf.urls import url,include
from django.conf import settings
from . import views

urlpatterns = [
        url('^$',views.home,name='home'),
        url('hoods', views.all_hoods, name='hoods'),
        url('business/view',views.display_business,name = 'viewbiz'),
        url('business/',views.create_business,name = 'business'),
        url('accounts/', include('registration.backends.simple.urls')),
        url('profile/create/',views.create_profile,name = 'profile'),
        url('showprofile/(?P<id>\d+)', views.display_profile, name='showprofile'),
        url('new/post$', views.new_post, name='newpost'),
        url('join/(\d+)', views.join, name='joinHood'),
        url('createHood/$', views.createHood, name='createHood'),
        url('comment/(?P<post_id>\d+)', views.comment, name='comment'),
        url('search/$', views.search, name='search'),

]

if settings.DEBUG:
    urlpatterns+= static(settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)