from __future__ import absolute_import, print_function, unicode_literals

import rospkg
import rospy
from sensor_msgs.msg import CameraInfo
from sensor_msgs.srv import SetCameraInfo
from sensor_msgs.srv import SetCameraInfoResponse

import os
import errno
import yaml

default_camera_info_url = "file://${ROS_HOME}/camera_info/${NAME}.yaml";

# parseURL() type codes:
URL_empty = 0                   # empty string
URL_file = 1                    # file:
URL_package = 2                 # package:
URL_invalid = 3                 # anything >= is invalid

class CameraInfoError(Exception):
    """
    ..exception: CameraInfoError

    Base class for exceptions in this module.
    """
    pass

class CameraInfoMissingError(CameraInfoError):
    """
    ..exception: CameraInfoMissingError

    Exception raised when CameraInfo has not been loaded.
    """
    pass

class CameraInfoManager():
    """
    :class:`CameraInfoManager` provides ROS CameraInfo support for
    Python camera drivers. It handles the `sensor_msgs/SetCameraInfo`_
    service requests, saving and restoring `sensor_msgs/CameraInfo`_
    data.

    :param cname: camera name.
    :param url: Uniform Resource Locator for camera calibration data.

    .. describe:: str(camera_info_manager_obj)

       :returns: String representation of :class:`CameraInfoManager` object.

    **ROS Service**

    - set_camera_info (`sensor_msgs/SetCameraInfo`_) stores
                      calibration information

    Typically, these service requests are made by a calibration
    package, such as:

    - http://www.ros.org/wiki/camera_calibration

    The calling node *must* invoke `rospy.spin()` in some thread, so
    :class:`CameraInfoManager` gets called to handle arriving service
    requests.

    **Camera Name**

    The device driver sets a camera name via the
    :class:`CameraInfoManager` constructor or the
    :py:meth:`setCameraName` method.  This name is written when
    CameraInfo is saved, and checked when data are loaded, with a
    warning logged if the name read does not match.

    Syntax: a camera name contains any combination of alphabetic,
            numeric and '_' characters.  Case is significant.

    Camera drivers may use any syntactically valid name they please.
    Where possible, it is best for the name to be unique to the
    device, such as a GUID, or the make, model and serial number.  Any
    parameters that affect calibration, such as resolution, focus,
    zoom, etc., may also be included in the name, uniquely identifying
    each CameraInfo file.

    The camera name can be resolved as part of the URL, allowing
    direct access to device-specific calibration information.

    **Uniform Resource Locator**

    The location for getting and saving calibration data is expressed
    by Uniform Resource Locator.  The driver defines a URL via the
    :class:`CameraInfoManager` constructor or the :py:meth:`setURL`
    method.  Many drivers provide a `~camera_info_url` parameter so
    users may customize this URL, but that is handled outside this
    class.

    Camera calibration information is stored in YAML format.

    Example URL syntax:

    - `file:///full/path/to/local/file.yaml`
    - `package://camera_info_manager_py/tests/test_calibration.yaml`
    - `package://ros_package_name/calibrations/camera3.yaml`

    The `file:` URL specifies a full path name in the local system.
    The `package:` URL is handled the same as `file:`, except the path
    name is resolved relative to the location of the named ROS
    package, which must be reachable via `$ROS_PACKAGE_PATH`.

    The URL may contain substitution variables delimited by `${...}`,
    including:

    - ${NAME} resolved to the current camera name defined by the
              device driver.
    - ${ROS_HOME} resolved to the `$ROS_HOME` environment variable if
                  defined, `~/.ros` if not.

    Resolution is done in a single pass through the URL string.
    Variable values containing substitutable strings are not resolved
    recursively.  Unrecognized variable names are treated literally
    with no substitution, but an error is logged.

    Examples with variable substitution:

    - `package://my_cameras/calibrations/${NAME}.yaml`
    - `file://${ROS_HOME}/camera_info/left_front_camera.yaml`

    The default URL is:

    - `file://${ROS_HOME}/camera_info/${NAME}.yaml`

    If that file exists, its contents are used. Any new calibration
    will be stored there, missing parent directories being created if
    necessary and possible.

    **Loading Calibration Data**

    Unlike the `C++ camera_info_manager`_, this Python implementation
    loads nothing until the :py:meth:`loadCameraInfo` method is
    called.  It is an error to call :py:meth:`getCameraInfo`, or
    :py:meth:`isCalibrated` before that is done.

    If the URL or camera name changes, :py:meth:`loadCameraInfo` must
    be called again before the data are accessible.

    """
    def __init__(self, cname='camera', url='',namespace=''):
        """Constructor.
        """
        self.cname = cname
        self.url = url
        self.camera_info = None

        # advertise set_camera_info service
        rospy.logdebug('set_camera_info service declared')
        self.svc = rospy.Service(namespace+'set_camera_info', SetCameraInfo,
                                 self.setCameraInfo)

    def __str__(self):
        """:returns: String representation of :class:`CameraInfoManager` """
        return '[' + self.cname + ']' + str(self.utm)

    def getCameraInfo(self):
        """ Get the current camera calibration.

        The :py:meth:`loadCameraInfo` must have been called since the
        last time the camera name or URL changed.

        :returns: `sensor_msgs/CameraInfo`_ message.

        :raises: :exc:`CameraInfoMissingError` if camera info not up
                 to date.

        """
        if self.camera_info is None:
            raise CameraInfoMissingError('Calibration missing, loadCameraInfo() needed.')
        return self.camera_info

    def getCameraName(self):
        """ Get the current camera name.

        :returns: camera name string
        """
        return self.cname

    def getURL(self):
        """ Get the current calibration URL.

        :returns: URL string without variable expansion.
        """
        return self.url

    def isCalibrated(self):
        """ Is the current CameraInfo calibrated?

        The :py:meth:`loadCameraInfo` must have been called since the
        last time the camera name or URL changed.

        :returns: True if camera calibration exists;
                  False for null calibration.

        :raises: :exc:`CameraInfoMissingError` if camera info not up
                 to date.

        """
        if self.camera_info is None:
            raise CameraInfoMissingError('Calibration missing, ' +
                                         'loadCameraInfo() needed.')
        return self.camera_info.K[0] != 0.0

    def _loadCalibration(self, url, cname):
        """ Load calibration data (if any available).

        This method updates self.camera_info, if possible, based on
        the url and cname parameters.  An empty or non-existent
        calibration is *not* considered an error, a null
        `sensor_msgs/CameraInfo`_ being provided in that case.

        :param url: Uniform Resource Locator for calibration data.
        :param cname: Camera name.

        :raises: :exc:`IOError` if an existing calibration is unreadable.
        :raises: :exc:`CameraInfoMissingError` if a `package:` URL is
                 inaccessible.
        """
        resolved_url = resolveURL(url, cname)
        url_type = parseURL(resolved_url)

        if url_type == URL_empty:
            self._loadCalibration(default_camera_info_url, cname)
            return

        rospy.loginfo('camera calibration URL: ' + resolved_url)

        if url_type == URL_file:
            self.camera_info = loadCalibrationFile(resolved_url[7:], cname)

        elif url_type == URL_package:
            filename = getPackageFileName(resolved_url)
            if filename == '':          # package not resolved
                raise CameraInfoMissingError('Calibration package missing.')
            self.camera_info = loadCalibrationFile(filename, cname)

        else:
            rospy.logerr("Invalid camera calibration URL: " + resolved_url)
            self.camera_info = CameraInfo()

    def loadCameraInfo(self):
        """ Load currently configured calibration data (if any).

        This method updates camera_info, if possible, based on the
        currently-configured URL and camera name.  An empty or
        non-existent calibration is *not* considered an error; a null
        `sensor_msgs/CameraInfo`_ being provided in that case.

        :raises: :exc:`IOError` if an existing calibration is unreadable.

        """
        self._loadCalibration(self.url, self.cname)

    def setCameraInfo(self, req):
        """ Callback for SetCameraInfo request.

        :param req: SetCameraInfo request message.
        :returns: SetCameraInfo response message, success is True if
                  message handled.

        :post: camera_info updated, can be used immediately without
               reloading.
        """
        rospy.logdebug('SetCameraInfo received for ' + self.cname)
        self.camera_info = req.camera_info
        rsp = SetCameraInfoResponse()
        rsp.success = saveCalibration(req.camera_info,
                                      self.url, self.cname)
        if not rsp.success:
            rsp.status_message = "Error storing camera calibration."
        return rsp

    def setCameraName(self, cname):
        """ Set a new camera name.

        :param cname: camera name to use for saving calibration data

        :returns: True if new name has valid syntax; valid names
                  contain only alphabetic, numeric, or '_' characters.

        :post: camera name updated, if valid. A new name may affect
               the URL, so camera_info will have to be reloaded before
               being used again.

        """
        # validate name
        if cname == '':
            return False        # name may not be empty
        for ch in cname:
            if not ch.isalnum() and ch != '_':
                return False    # invalid character

        # name is valid, use it
        if self.cname != cname:
            self.cname = cname
            self.camera_info = None     # missing if name changed
        return True

    def setURL(self, url):
        """ Set the calibration URL.

        :param cname: camera name to use for saving calibration data

        :returns: True if new name has valid syntax.

        :post: URL updated, if valid. A new value may change the
               camera_info, so it will have to be reloaded before
               being used again.

        """
        if parseURL(resolveURL(url, self.cname)) >= URL_invalid:
            return False                # syntax error

        # URL looks valid, so use it
        if self.url != url:
            self.url = url
            self.camera_info = None     # missing if URL changed
        return True

# related utility functions

def genCameraName(from_string):
    """ Generate a valid camera name.

    Valid names contain only alphabetic, numeric, or '_'
    characters. All invalid characters in from_string are replaced
    by an '_'.

    :param from_string: string from which to base camera name.

    :returns: a valid camera name based on from_string.

    """
    if from_string == '':
        return '_'          # name may not be empty

    retval = ''
    for i in range(len(from_string)):
        if not from_string[i].isalnum() and from_string[i] != '_':
            retval += '_'
        else:
            retval += from_string[i]
    return retval

def getPackageFileName(url):
    """ Get file name corresponding to a `package:` URL.

    `param url` fully-resolved Uniform Resource Locator
    `returns` file name if package found, "" otherwise

    """
    # Scan URL from after "package://" until next '/' and extract
    # package name.  The parseURL() already checked that it's present.
    prefix_len = len("package://")
    rest = url.find('/', prefix_len)
    package = url[prefix_len: rest]

    # Look up the ROS package path name.
    pkgPath = ""
    rp = rospkg.RosPack()
    try:
        pkgPath = rp.get_path(package)
        pkgPath += url[rest:]

    except rospkg.ResourceNotFound:
        rospy.logwarn("unknown package: " + package + " (ignored)")

    return pkgPath

def loadCalibrationFile(filename, cname):
    """ Load calibration data from a file.

    This function returns a `sensor_msgs/CameraInfo`_ message, based
    on the filename parameter.  An empty or non-existent file is *not*
    considered an error; a null CameraInfo being provided in that
    case.

    :param filename: location of CameraInfo to read
    :param cname: Camera name.
    :returns: `sensor_msgs/CameraInfo`_ message containing calibration,
              if file readable; null calibration message otherwise.
    :raises: :exc:`IOError` if an existing calibration file is unreadable.

    """
    ci = CameraInfo()
    try:
        f = open(filename)
        calib = yaml.load(f)
        if calib is not None:
            if calib['camera_name'] != cname:
                rospy.logwarn("[" + cname + "] does not match name " +
                              calib['camera_name'] + " in file " + filename)

            # fill in CameraInfo fields
            ci.width = calib['image_width']
            ci.height = calib['image_height']
            ci.distortion_model = calib['distortion_model']
            ci.D = calib['distortion_coefficients']['data']
            ci.K = calib['camera_matrix']['data']
            ci.R = calib['rectification_matrix']['data']
            ci.P = calib['projection_matrix']['data']

    except IOError:                     # OK if file did not exist
        pass

    return ci

def parseURL(url):
    """ Parse calibration Uniform Resource Locator.

    `param url`: string to parse
    `returns` URL type code

    `note`: Unsupported URL types have codes >= URL_invalid.

    """
    if url == "":
        return URL_empty

    if url[0:8].upper() == "FILE:///":
        return URL_file;

    if url[0:10].upper() == "PACKAGE://":
        # look for a '/' following the package name, make sure it is
        # there, the name is not empty, and something follows it

        rest = url.find('/', 10);
        if rest < len(url)-1 and rest >= 0:
            return URL_package;

    return URL_invalid;

def resolveURL(url, cname):
    """ Resolve substitution strings in Uniform Resource Locator.

    :param url: URL to resolve, which may include `${...}`
                substitution variables.
    :param cname: camera name for resolving `${NAME}` variables.

    :returns: a copy of the URL with any variable information resolved.

    """
    resolved = ''                # resolved URL to return
    rest = 0                     # index of remaining string to parse

    while True:

        # find the next '$' in the URL string
        dollar = url.find('$', rest)

        if dollar == -1:                # no more '$'s there?
            resolved += url[rest:]
            return resolved

        # copy characters up to the next '$'
        resolved += url[rest:dollar]

        if url[dollar+1:dollar+2] != '{':
            #  no '{' follows, so keep the '$'
            resolved += '$'

        elif url[dollar+1:dollar+7] == '{NAME}':
            # substitute camera name
            resolved += cname
            dollar += 6

        elif url[dollar+1:dollar+11] == '{ROS_HOME}':
            # substitute $ROS_HOME
            ros_home = os.environ.get('ROS_HOME')
            if ros_home is None:
                ros_home = os.environ.get('HOME')
                if ros_home is None:
                    rospy.logwarn('[CameraInfoManager]' +
                                  ' unable to resolve ${ROS_HOME}')
                    ros_home = '${ROS_HOME}' # retain it unresolved
                else:
                    ros_home += '/.ros'
            resolved += ros_home
            dollar += 10

        else:
            # not a valid substitution variable
            rospy.logwarn("[CameraInfoManager]" +
                         " invalid URL substitution (not resolved): "
                         + url);
            resolved += "$";            # keep the bogus '$'

        # look for next '$'
        rest = dollar + 1

def saveCalibration(new_info, url, cname):
    """ Save calibration data.

    This function writes new calibration information to the
    location defined by the url and cname parameters, if possible.

    :param new_info: `sensor_msgs/CameraInfo`_ to save.
    :param url: Uniform Resource Locator for calibration data (if
                empty use file://${ROS_HOME}/camera_info/${NAME}.yaml).
    :param cname: Camera name.
    :returns: True if able to save the data.
    """
    success = False
    resolved_url = resolveURL(url, cname)
    url_type = parseURL(resolved_url)

    if url_type == URL_empty:
        return saveCalibration(new_info, default_camera_info_url,
                               cname)

    rospy.loginfo('writing calibration data to URL: ' + resolved_url)

    if url_type == URL_file:
        success = saveCalibrationFile(new_info, resolved_url[7:], cname)

    elif url_type == URL_package:
        filename = getPackageFileName(resolved_url)
        if filename == '':          # package not resolved
            rospy.logerr('Calibration package missing: ' +
                         resolved_url + ' (ignored)')
            # treat it like an empty URL
            success = saveCalibration(new_info, default_camera_info_url,
                                      cname)
        else:
            success = saveCalibrationFile(new_info, filename, cname)

    else:
        rospy.logerr("Invalid camera calibration URL: " + resolved_url)
        # treat it like an empty URL
        success = saveCalibration(new_info, default_camera_info_url,
                                  cname)
    return success

def saveCalibrationFile(ci, filename, cname):
    """ Save calibration data to a YAML file.

    This function writes the new calibration information to a YAML
    file, if possible.

    :param ci: `sensor_msgs/CameraInfo`_ to save.
    :param filename: local file to store data.
    :param cname: Camera name.
    :returns: True if able to save the data.
    """
    # make sure the directory exists and the file is writable
    f = None
    try:
        f = open(filename, 'w')
    except IOError as e:
        if e.errno in set([errno.EACCES, errno.EPERM]):
            pass
        elif e.errno in set([errno.ENOENT]):
            # Find last slash in the name.  The URL parser ensures
            # there is at least one '/', at the beginning.
            last_slash = filename.rfind('/')
            if last_slash < 0:
                rospy.logerr("filename [" + filename + "] has no '/'")
                return False    # not a valid URL

            # try to create the directory and all its parents
            dirname = filename[0:last_slash+1]
            try:
                os.makedirs(dirname)
            except OSError:
                rospy.logerr("unable to create path to directory [" +
                             dirname + "]")
                return False

            # try again to create the file
            try:
                f = open(filename, 'w')
            except IOError:
                pass

    if f is None:               # something went wrong above?
        rospy.logerr("file [" + filename + "] not accessible")
        return False            # unable to write this file

    # make calibration dictionary from CameraInfo fields and camera name
    calib = {'image_width': ci.width,
             'image_height': ci.height,
             'camera_name': cname,
             'distortion_model': ci.distortion_model,
             'distortion_coefficients': {'data': ci.D},
             'camera_matrix': {'data': ci.K},
             'rectification_matrix': {'data': ci.R},
             'projection_matrix': {'data': ci.P}}

    try:
        rc = yaml.dump(calib, f)
        return True

    except IOError:
        return False            # fail if unable to write file


from acts.test_decorators import test_tracker_info
from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
from acts_contrib.test_utils.bt.GattConnectedBaseTest import GattConnectedBaseTest
from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic
from acts_contrib.test_utils.bt.bt_constants import gatt_descriptor
from acts_contrib.test_utils.bt.bt_constants import gatt_event
from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings
from acts_contrib.test_utils.bt.bt_constants import gatt_char_desc_uuids
from math import ceil

class GattNotifyTest(GattConnectedBaseTest):
    @BluetoothBaseTest.bt_test_wrap
    @test_tracker_info(uuid='e0ba60af-c1f2-4516-a5d5-89e2def0c757')
    def test_notify_char(self):
        """Test notify characteristic value.

        Test GATT notify characteristic value.

        Steps:
        1. Central: write CCC - register for notifications.
        2. Peripheral: receive CCC modification.
        3. Peripheral: send characteristic notification.
        4. Central: receive notification, verify it's conent matches what was
           sent

        Expected Result:
        Verify that notification registration and delivery works.

        Returns:
          Pass if True
          Fail if False

        TAGS: LE, GATT, Characteristic
        Priority: 0
        """
        #write CCC descriptor to enable notifications
        self.cen_ad.droid.gattClientDescriptorSetValue(
            self.bluetooth_gatt, self.discovered_services_index,
            self.test_service_index, self.NOTIFIABLE_CHAR_UUID,
            gatt_char_desc_uuids['client_char_cfg'],
            gatt_descriptor['enable_notification_value'])

        self.cen_ad.droid.gattClientWriteDescriptor(
            self.bluetooth_gatt, self.discovered_services_index,
            self.test_service_index, self.NOTIFIABLE_CHAR_UUID,
            gatt_char_desc_uuids['client_char_cfg'])

        #enable notifications in app
        self.cen_ad.droid.gattClientSetCharacteristicNotification(
            self.bluetooth_gatt, self.discovered_services_index,
            self.test_service_index, self.NOTIFIABLE_CHAR_UUID, True)

        event = self._server_wait(gatt_event['desc_write_req'])

        request_id = event['data']['requestId']
        bt_device_id = 0
        status = 0
        #confirm notification registration was successful
        self.per_ad.droid.gattServerSendResponse(
            self.gatt_server, bt_device_id, request_id, status, 0, [])
        #wait for client to get response
        event = self._client_wait(gatt_event['desc_write'])

        #set notified value
        notified_value = [1, 5, 9, 7, 5, 3, 6, 4, 8, 2]
        self.per_ad.droid.gattServerCharacteristicSetValue(
            self.notifiable_char_index, notified_value)

        #send notification
        self.per_ad.droid.gattServerNotifyCharacteristicChanged(
            self.gatt_server, bt_device_id, self.notifiable_char_index, False)

        #wait for client to receive the notification
        event = self._client_wait(gatt_event['char_change'])
        self.assertEqual(notified_value, event["data"]["CharacteristicValue"])

        return True


import random

# Documentation is like sex.
# When it's good, it's very good.
# When it's bad, it's better than nothing.
# When it lies to you, it may be a while before you realize something's wrong.

with open("score.txt", "r") as score_file:
    old_scores_raw = score_file.read()
old_scores_raw = old_scores_raw.split("\n")

old_scores = []
for i in old_scores_raw[:-1]:
    old_scores.append(int(i))

# old_scores = [int(x) for x in old_scores_raw[:-1]]  # List comprehension - isto kot zgornja for zanka

min_score = min(old_scores)
print(f"Best result so far {min_score}")

secret_num = random.randint(1, 100)

attempts = 0
while True:
    guess = int(input("Ugibaj stevilo med 1 in 100: "))
    attempts += 1  # attempts = attempts + 1

    if guess < secret_num:
        print("Guess higher")
    elif guess > secret_num:
        print("Guess lower")
    else:
        print("Bravo, you guessed in {0} tries".format(attempts))
        break

with open("score.txt", "a") as score_file:
    score_file.write(f"{attempts}\n")


# 裁剪指定坐标
from PIL import Image
import cv2 as cv
Image.MAX_IMAGE_PIXELS = 1000000000

def px_to_mm(i, dpi):
    return round(i / dpi * 25.4)

def slt(path, savepath,type='png'):
    im = Image.open(path)
    H, W, = im.size
    new_H, new_W = px_to_mm(H, 300), px_to_mm(W, 300)
    im.thumbnail((new_H, new_W))
    im.save(savepath, type)

def qiege(path, savepath, x0, y0, x1, y1):
    # 左上角为起点，横坐标为x,坐标为y
    img = img = Image.open(path)
    i = img.crop((x0, y0, x1, y1))
    i.save(savepath,dpi=img.info['dpi'])

def qiege2(path, savepath,  x0,x1,y0,y1,):
    # 左上角为起点，横坐标为x,坐标为y
    img = cv.imread(path, 1)
    cropped = img[y0:y1, x0:x1]  # 裁剪坐标为[y0:y1, x0:x1]
    cv.imwrite(savepath, cropped)

# slt('./32.tif', './4.png',type='png')
# qiege2('./1.tif','./33.tif', 0, 14409,0, 14409,)
qiege('./1.tif','./34.tif', 0,0,5000, 14409)


from os import listdir
from datetime import datetime
import pandas as pd

def get_logs_data(logs_fin='checkpoints/sigmorphon20-task0/',
                  dev_lang_fin='task0-data/DEVELOPMENT-LANGUAGES/',
                  save=True, print_progress=True):
    '''returns data from the outputted logs file,
    and the development languages.

    Parameters
    ----------
    logs_fin : leave as checkpoints, or <path passed>/mtype/foo.log
    dev_lang_fin : leave as blank, or <path passed>/family/files
    save : save dataframe to csv, default is False
    print_progress : print progress messages, default is False

    Returns
    -------
    pandas dataframe of all completed models for each language and their statistics
    '''

    def get_time(time):
        '''gets time from 'h:m:s'
        '''

        if len(time) > 3:
            # more than one day occured
            days = int(time[1])
            hms = [int(i) for i in time[3].split(':')]
            hms = [hms[0] + (24*days), hms[1], hms[2]]
        else:
            hms = [int(i) for i in time[1].split(':')]

        # convert hms to seconds
        seconds = (hms[0]*60*60) + (hms[1]*60) + (hms[2])

        return seconds

    def get_log_dict(mtype, logfin):
        '''returns dictionary of {language abbreviation, }
        '''

        if 'checkpoints' not in logfin:
            lang_abv = logfin.split('/')[2].split('.')[0]
        else:
            lang_abv = logfin.split('/')[-1].split('.')[0]

        with open(logfin, 'r', errors='ignore') as fin:
            load_line = None  # line for last model being loaded
            last_epoch_line = None  # line for last epoch
            for line in fin:
                if 'load model' in line:
                    load_line = line
                if load_line is None:
                    if 'at epoch' in line and '-1' not in line:
                        last_epoch_line = line
                pass
            fin.close()

        try:
            date_ran = str(line).split('-')[1].split(' ')[1]
            accuracy = float(str(line).split('-')[3].split(' ')[4])
            distance = float(str(line).split('-')[3].split(' ')[6].strip())
            if load_line is not None:
                nll = float(load_line.split('_')[1].split('acc')[0][0:-1])
                epoch_chosen = int(load_line.split('_')[-1].strip())
            if last_epoch_line is not None:
                totepoch = int(last_epoch_line.split(' ')[-1].strip())

            # adjust time based off num days ran
            # passed str object of 'h:m:s' or 'x days, h:m:s'
            time_ran = get_time(str(line).split('-')[2].split(' '))
        except:
            print(f'{lang_abv} is still in progress, cannot get stats.')
            date_ran = None
            accuracy = None
            distance = None
            nll = None
            epoch_chosen = None
            totepoch = None
            time_ran = None
            pass

        return {
            "lang": lang_abv,
            "mtype": mtype,
            "date": date_ran,
            "time_ran(s)": time_ran,
            "tot_epoch": totepoch,
            "mepoch": epoch_chosen,
            "nll": nll,
            "accuracy": accuracy,
            "distance": distance
        }

    def get_lang_dict(file, path):
        '''returns dictionary of {language, filetype, number of lines in file}
        '''
        family = path.split('/')[2]
        lang_abv = file.split('.')[0]
        ftype = file.split('.')[1]
        length = sum(1 for line in open(path + file, errors='ignore'))

        return {
            "family": family,
            "lang": lang_abv,
            "ftype": ftype,
            "nwords": length
        }

    def check_hall(mtype, file):
        '''checks to make sure only hall modeltypes are
        assigned to hall filetypes
        '''
        if 'hall' in mtype and 'hall' not in file:
            return False
        elif 'hall' not in mtype and 'hall' in file:
            return False
        else:
            return True

    def get_filepaths(logs_fin):
        '''checks if filepath passed is checkpoints or
        usercreated folder of '<path passed>/mtype/foo.log'
        '''

        if logs_fin in 'checkpoints/sigmorphon20-task0/':
            # filepath
            model_types = [folder1
                           for folder1 in listdir(logs_fin)]

            file_paths = [logs_fin + folder1 + '/' + folder2 + '/'
                          for folder1 in listdir(logs_fin)
                          for folder2 in listdir(logs_fin + folder1 + '/')]
        else:
            model_types = [folder1
                           for folder1 in listdir(logs_fin)]

            file_paths = [logs_fin + folder1 + '/'
                          for folder1 in listdir(logs_fin)]

        return model_types, file_paths

    def get_model_df(mtype, model_fin):
        '''pass in modeltype and filepath to folder of logs,
        returns dataframe
        '''

        languages = [file.split('.')[0]
                     for file in listdir(model_fin)
                     if 'log' in file]

        log_stats = [get_log_dict(mtype, model_fin + file)
                     for file in listdir(model_fin)
                     if 'log' in file]

        lang_stats = [get_lang_dict(file, dev_lang_fin + folder + '/')
                      for folder in listdir(dev_lang_fin)
                      for file in listdir(dev_lang_fin + folder)
                      if file[0:3] in languages and check_hall(mtype, file)
                      ]

        log_df = pd.DataFrame(log_stats)
        lang_df = pd.DataFrame(lang_stats)

        dataframe = pd.merge(lang_df, log_df, on='lang')

        return dataframe

    def main(logs_fin, dev_lang_fin, save, print_progress):

        if print_progress is True:
            print('Script is starting, getting modeltypes and filepaths.')

        model_types, file_paths = get_filepaths(logs_fin)

        df_list = [get_model_df(mtype, file_paths[i])
                   for i, mtype in enumerate(model_types)]

        if print_progress is True:
            print('All dataframes successfully created, merging dataframes.')

        dataframe = pd.concat(df_list).sort_values('lang')

        if save is True:
            savefolder = 'scraped_data'

            try:
                mkdir(savefolder)
            except:
                pass

            date = datetime.today().strftime('%Y-%m-%d')
            fp = savefolder + '/' + date + '-completed_log_data.csv'
            dataframe.to_csv(fp, index=False)
            print(f'Dataframe is being saved to {fp}')

        if print_progress is True:
            print('Returning dataframe object.')
            print(dataframe.head(25))
        return dataframe

    main(logs_fin, dev_lang_fin, save, print_progress)

get_logs_data()


from tkinter import *
from tkinter.filedialog import askopenfilename, asksaveasfilename
import tkinter.scrolledtext as st

win = Tk()
win.iconbitmap('c:/Users/Jonathan/Desktop/note.ico')
win.title('Sem título - Bloco de notas')
win.geometry('944x450')
win.columnconfigure(0, weight=1)
win.rowconfigure(0, weight=1)

def novo():
    txt.delete(1.0, END)
    win.title('Sem título - Bloco de notas')
    b0.entryconfig(2, state=DISABLED)
def abrir():
    global path
    filepath = askopenfilename(
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
    )
    if not filepath:
        return
    txt.delete(1.0, END)
    with open(filepath, "r") as input_file:
        text = input_file.read()
        txt.insert(END, text)
        path = filepath
    win.title(f"{path} - Bloco de notas")
    b0.entryconfig(2, state=NORMAL)
def salvar():
    arquivo = open(f"{path}", "w")
    text = txt.get(1.0, END)
    arquivo.write(text)
    win.title(f"{path} - Bloco de notas")
def salvar_como():
    global path
    filepath = asksaveasfilename(
        defaultextension="txt",
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")],
    )
    if not filepath:
        return
    with open(filepath, "w") as output_file:
        text = txt.get(1.0, END)
        output_file.write(text)
        path = filepath
    win.title(f"{path} - Bloco de notas")
    b0.entryconfig(2, state=NORMAL)
def sair():
    win.destroy()

def ajuda():
    window = Toplevel(win)
    window.title('Sobre o Bloco de notas')
    window.geometry('458x375')
    window.resizable(0,0)
    window.focus()

mn = Menu(win)
# Cascata do botão 0
b0 = Menu(mn, tearoff=0)
b0.add_command(label='Novo', command=novo)
b0.add_command(label='Abrir...', command=abrir)
b0.add_command(label='Salvar', command=salvar, state=DISABLED)
b0.add_command(label='Salvar como...', command=salvar_como)
b0.add_separator()
b0.add_command(label='Configurar Página...')
b0.add_command(label='Imprimir...')
b0.add_separator()
b0.add_command(label='Sair', command=sair)
# Cascata do botão 1
b1 = Menu(mn, tearoff=0)
b1.add_command(label='Desfazer', state=DISABLED)
b1.add_separator()
b1.add_command(label='Recortar', state=DISABLED)
b1.add_command(label='Copiar', state=DISABLED)
b1.add_command(label='Colar', state=DISABLED)
b1.add_command(label='Excluir', state=DISABLED)
b1.add_separator()
b1.add_command(label='Localizar...')
b1.add_command(label='Localizar próxima')
b1.add_command(label='Substituir...')
b1.add_command(label='Ir para...')
b1.add_separator()
b1.add_command(label='Selecionar tudo')
b1.add_command(label='Hora/data')
# Cascata do botão 2
b2 = Menu(mn, tearoff=0)
b2.add_command(label='Quebra automática de linha')
b2.add_command(label='Fonte...')
# Cascata do botão 3
b3 = Menu(mn, tearoff=0)
b3.add_command(label='Barra de status')
# Cascata do botão 4
b4 = Menu(mn, tearoff=0)
b4.add_command(label='Exibir Ajuda')
b4.add_separator()
b4.add_command(label='Sobre o Bloco de notas', command=ajuda)
# Botões do menu
mn.add_cascade(label='Arquivo', menu=b0)
mn.add_cascade(label='Editar', menu=b1)
mn.add_cascade(label='Formatar', menu=b2)
mn.add_cascade(label='Exibir', menu=b3)
mn.add_cascade(label='Ajuda', menu=b4)
win.config(menu=mn)
# Área de texto
def currentCur(event):
    cur = txt.index(CURRENT).split('.')
    if cur[1] == '0':
        cur[1] = '1'
    bs0.config(text='Ln '+cur[0]+', Col '+cur[1])
txt = st.ScrolledText(win)
txt.grid(column=0,row=0, sticky='nsew')
txt.bind('<Key>', currentCur)
txt.bind('<Button>', currentCur)
# Barra de status
bs0 = Label(win, text='Ln 1, Col 1', anchor=E)
bs0.grid(column=0,row=1, padx=(1, 165), sticky='nsew')

win.mainloop()


from tkinter import *
from tkcalendar import DateEntry
from tkinter import messagebox
import datetime
import backend
from btn_hover import *

def main(root):
# def main():
  fontUsed = "Segoe UI"

  def issueBtnClick():
    issue = True
    for value in bookstrEntries.values():
      if value.get()=='':
        issue = False
    for value in stdstrEntries.values():
      if value.get()=='':
        issue = False
    if issue:
      if int(avail_copies.get())>0:
        try:
          backend.issue_book(bookID.get(), sid.get(), issueDate.get(), dueDate.get())
          messagebox.showinfo("Done", "Book issued successfully!", parent=win)
          win.destroy()
        except Exception as e:
          if "unique" in str(e).lower():
            messagebox.showinfo("Issued", "Specified Book is already issued to the specified student!", parent=win)
      else:
        messagebox.showinfo("Copies", "Not enough copies available!", parent=win)
    else:
      messagebox.showinfo("Search", "Search first!", parent=win)

  def booksearchBtnClick():
    if bookID.get()!='':
      if bookID.get().isnumeric():
        entries = backend.search_book_issue(bookID.get())
        if entries!=None:
          bookID.config(state='disabled')

          i = 0
          for key in bookstrEntries.keys():
            bookstrEntries[key].set(entries[i])
            i+=1
        else:
          messagebox.showerror("Not found", "Specified BookID not found!", parent=win)
          for key in bookstrEntries.keys():
            bookstrEntries[key].set('')
      else:
        messagebox.showinfo("BookID", "BookID must be a number!", parent=win)
    else:
      messagebox.showinfo("BookID", "Enter bookID first!", parent=win)

  def stdsearchBtnClick():
    if sid.get()!='':
      if sid.get().isnumeric():
        entries = backend.search_student_issue(sid.get())
        if entries!=None:
          sid.config(state='disabled')

          i = 0
          for key in stdstrEntries.keys():
            stdstrEntries[key].set(entries[i])
            i+=1
        else:
          messagebox.showerror("Not found", "Specified SID not found!", parent=win)
          for key in stdstrEntries.keys():
            stdstrEntries[key].set('')
      else:
        messagebox.showinfo("SID", "SID must be a number!", parent=win)
    else:
      messagebox.showinfo("SID", "Enter SID first!", parent=win)

  def bookresetBtnClick():
    sbookID.set("")
    bookID.config(state='normal')
    for key in bookstrEntries.keys():
      bookstrEntries[key].set("")
    win.focus()

  def stdresetBtnClick():
    ssid.set("")
    sid.config(state='normal')
    for key in stdstrEntries.keys():
      stdstrEntries[key].set("")
    win.focus()

  win = Toplevel(root)
  # win = Tk()
  bookstrEntries={"sisbn": StringVar(), "stitle": StringVar(), "sauthor": StringVar(), "spublisher": StringVar(), "sedition": StringVar(), "savail_copies": StringVar()}
  stdstrEntries={"sfname": StringVar(), "slname": StringVar(), "semail": StringVar(), "sphno": StringVar()}

  win.grab_set()
  win.focus()
  win.title("Issue Book")
  width = 700
  height = 550
  x = ((win.winfo_screenwidth() - width)//2)-30
  y = ((win.winfo_screenheight() - height)//2)-30
  win.geometry(f"{width}x{height}+{x}+{y}")
  win.resizable(0, 0)
  bgimage=PhotoImage(file="images/issue_book.png")
  bg=Label(win, image=bgimage)
  bg.place(x=0,y=0)

  sbookID = StringVar()
  bookID = Entry(win, font=(fontUsed, 14), width=8, textvariable=sbookID)
  bookID.place(x=380,y=14)

  booksearchBtn = Button(win, text="Search", bg='#655bd6', fg='white', font=(fontUsed, 11), borderwidth=0, width=8, command=booksearchBtnClick)
  booksearchBtn.place(x=475, y=13)

  booksearchBtn.bind("<Enter>", lambda e: btnMouseEnter(booksearchBtn))
  booksearchBtn.bind("<Leave>", lambda e: btnMouseLeave(booksearchBtn))

  isbn = Entry(win, font=(fontUsed, 11), width=23, textvariable=bookstrEntries["sisbn"], state='disabled')
  isbn.place(x=350,y=66)

  book_title = Entry(win, font=(fontUsed, 11), width=19, textvariable=bookstrEntries["stitle"], state='disabled')
  book_title.place(x=382,y=95)

  author = Entry(win, font=(fontUsed, 11), width=22, textvariable=bookstrEntries["sauthor"], state='disabled')
  author.place(x=358,y=123)

  publisher = Entry(win, font=(fontUsed, 11), width=20, textvariable=bookstrEntries["spublisher"], state='disabled')
  publisher.place(x=375,y=150)

  edition = Entry(win, font=(fontUsed, 11),width=22, textvariable=bookstrEntries["sedition"], state='disabled')
  edition.place(x=360,y=178)

  avail_copies = Entry(win, font=(fontUsed, 11),width=12, textvariable=bookstrEntries["savail_copies"], state='disabled')
  avail_copies.place(x=440,y=206)

  ssid = StringVar()
  sid = Entry(win, font=(fontUsed, 14), width=8, textvariable=ssid)
  sid.place(x=345,y=240)

  stdsearchBtn = Button(win, text="Search", bg='#655bd6', fg='white', font=(fontUsed, 11), borderwidth=0, width=8, command=stdsearchBtnClick)
  stdsearchBtn.place(x=440, y=239)

  stdsearchBtn.bind("<Enter>", lambda e: btnMouseEnter(stdsearchBtn))
  stdsearchBtn.bind("<Leave>", lambda e: btnMouseLeave(stdsearchBtn))

  fname = Entry(win, font=(fontUsed, 11), width=12, state='disabled', textvariable=stdstrEntries["sfname"])
  fname.place(x=392,y=292)
  lname = Entry(win, font=(fontUsed, 11), width=12, state='disabled', textvariable=stdstrEntries["slname"])
  lname.place(x=508,y=292)

  email = Entry(win, font=(fontUsed, 11), width=24, state='disabled', textvariable=stdstrEntries["semail"])
  email.place(x=352,y=346)
  phno = Entry(win, font=(fontUsed, 11), width=20, state='disabled', textvariable=stdstrEntries["sphno"])
  phno.place(x=384,y=375)

  strIssuedate = StringVar(value=datetime.date.today())
  issueDate = DateEntry(win, font=(fontUsed, 10), date_pattern="yyyy-mm-dd", state='disabled', textvariable=strIssuedate)
  issueDate.place(x=410,y=428)

  def change_cal_position(e):
    cal = dueDate._top_cal
    if cal.winfo_ismapped():
      cal.withdraw()
    else:
      x, y, _, _ = dueDate.bbox("insert")
      x += dueDate.winfo_rootx() - 50
      y += dueDate.winfo_rooty() - 206

      cal.geometry(f"+{x}+{y}")
      cal.deiconify()

  dueDate = DateEntry(win, font=(fontUsed, 10), date_pattern="yyyy-mm-dd", mindate=datetime.date.today()+datetime.timedelta(days=1))
  dueDate.place(x=410,y=460)
  dueDate.bind("<Button-1>", change_cal_position)

  issueBtn = Button(win, text="Issue Book", bg='#655bd6', fg='white', font=(fontUsed, 14), borderwidth=0, width=12, command=issueBtnClick)
  issueBtn.place(x=545,y=440)

  issueBtn.bind("<Enter>", lambda e: btnMouseEnter(issueBtn))
  issueBtn.bind("<Leave>", lambda e: btnMouseLeave(issueBtn))

  bookresetBtn = Button(win, text="Reset", bg='#655bd6', fg='white', font=(fontUsed, 11), borderwidth=0, width=8, command=bookresetBtnClick)
  bookresetBtn.place(x=555, y=13)

  bookresetBtn.bind("<Enter>", lambda e: btnMouseEnter(bookresetBtn))
  bookresetBtn.bind("<Leave>", lambda e: btnMouseLeave(bookresetBtn))

  stdresetBtn = Button(win, text="Reset", bg='#655bd6', fg='white', font=(fontUsed, 11), borderwidth=0, width=8, command=stdresetBtnClick)
  stdresetBtn.place(x=520, y=239)

  stdresetBtn.bind("<Enter>", lambda e: btnMouseEnter(stdresetBtn))
  stdresetBtn.bind("<Leave>", lambda e: btnMouseLeave(stdresetBtn))

  win.mainloop()

# main()


import scrapy
import re
from datetime import datetime

class AmazonSpider(scrapy.Spider):
    name = 'amazon'
    count = 0
    term = ''

    def __init__(self, *args, **kwargs):
        super(AmazonSpider, self).__init__(*args, **kwargs)

        self.term = kwargs.get('term')
        self.start_urls = ['https://www.amazon.es/s?k={}'.format(self.term)]

    def parse(self, response):
        for products in response.css('div.sg-col-inner'):
            self.count += 1
            prod_name = products.css('.a-size-base-plus.a-color-base.a-text-normal::text').get()
            prod_price = products.css('.a-price-whole::text').get()
            prod_price_hundred = -1
            prod_img = products.css('img.s-image').xpath('@src').get()
            prod_link = products.css('.a-link-normal.a-text-normal').xpath('@href').get()
            scrape_date_time = datetime.now()
            scrape_date = datetime.today().strftime('%Y-%m-%d')

            if prod_price is None:
                prod_price = 'Not specified'

            if prod_price != 'Not specified' and ',' in prod_price:
                prod_price = re.sub('\s+', '', prod_price)
                prod_price = prod_price.replace(',', '.')
                prod_price_hundred = round(float(prod_price), -2)

            if prod_link is not None:
                prod_link = 'https://www.amazon.es' + prod_link

            if self.term == 'favicon.ico':
                self.term = 'none'

            if prod_name is None:
                self.count -= 1
                yield
            else:
                yield {
                    'id': self.count,
                    'name': prod_name,
                    'price': prod_price,
                    'price_hundred': prod_price_hundred,
                    'image': prod_img,
                    'link': prod_link,
                    'store': 'Amazon ES',
                    'scrape_date_time': scrape_date_time,
                    'scrape_date': scrape_date,
                    'term': self.term
                }

        next_page = response.css('li.a-last>a').xpath('@href').get()
        print('https://www.amazon.es/' + next_page)
        if next_page is not None:
            yield scrapy.Request('https://www.amazon.es/' + next_page, callback=self.parse, dont_filter=True)

# command:  scrapy crawl amazon -a term="pc" -O products.json


import Field
from tkinter import *

class Player(Field.Field):
    def __init__(self, column_pos, row_pos, field: Field):
        self.field: Field = field
        self.polygon = None
        self.dir = 'right'
        self.turns = 0
        self.column_pos = column_pos
        self.row_pos = row_pos
        self.prev_column_pos = column_pos
        self.prev_row_pos = row_pos
        self.good_direction = True
        self.moves = 0
        self.coords = []
        self.get_player_coords()
        self.block_inputs = [0, 0, 0, 0, 0, 0]

    def get_player_coords(self):
        offset = 5
        x1 = (self.column_pos) * self.field.block_width + offset
        x2 = (self.column_pos) * self.field.block_width + self.field.block_width
        x3 = x1
        y1 = (self.row_pos) * self.field.block_height + offset
        y2 = (self.row_pos) * self.field.block_height + (self.field.block_height / 2) + (offset / 2)
        y3 = (self.row_pos) * self.field.block_height + self.field.block_height
        self.coords = {'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2, 'x3': x3, 'y3': y3}

    def get_block_inputs(self):
        self.block_inputs[0] = self.field.field_dict[self.column_pos][self.row_pos - 1]['block']
        self.block_inputs[1] = self.field.field_dict[self.column_pos + 1][self.row_pos]['block']
        self.block_inputs[2] = self.field.field_dict[self.column_pos][self.row_pos + 1]['block']
        self.block_inputs[3] = self.field.field_dict[self.column_pos - 1][self.row_pos]['block']
        if (self.dir == 'right'):
            self.block_inputs[4] = 1
            self.block_inputs[5] = 1
        elif (self.dir == 'left'):
            self.block_inputs[4] = -1
            self.block_inputs[5] = -1
        elif (self.dir == 'up'):
            self.block_inputs[4] = 1
            self.block_inputs[5] = -1
        elif (self.dir == 'down'):
            self.block_inputs[4] = -1
            self.block_inputs[5] = 1

    def new_player(self, canvas: Canvas):
        self.polygon = canvas.create_polygon(self.coords['x1'], self.coords['y1'], self.coords['x2'],
                                             self.coords['y2'], self.coords['x3'], self.coords['y3'],
                                             fill='Blue')

    def move_player(self, direction, canvas: Canvas):
        if direction == 'right':
            if (self.dir == 'up' or self.dir == 'down'):
                self.turns += 1
            canvas.move(self.polygon, self.field.block_width, 0)
            if (self.prev_column_pos == self.column_pos + 1):
                self.good_direction = not self.good_direction
            self.prev_column_pos = self.column_pos
            self.column_pos += 1
            self.dir = 'right'
        if direction == 'up':
            if (self.dir == 'right'):
                self.turns += 1
            canvas.move(self.polygon, 0, -self.field.block_height)
            if (self.prev_row_pos == self.row_pos - 1):
                self.good_direction = not self.good_direction
            self.prev_row_pos = self.row_pos
            self.row_pos -= 1
            self.dir = 'up'
        if direction == 'down':
            if (self.dir == 'right'):
                self.turns += 1
            canvas.move(self.polygon, 0, self.field.block_height)
            if (self.prev_row_pos == self.row_pos + 1):
                self.good_direction = not self.good_direction
            self.prev_row_pos = self.row_pos
            self.row_pos += 1
            self.dir = 'down'
        if direction == 'left':
            canvas.move(self.polygon, -self.field.block_width, 0)
            if (self.prev_column_pos == self.column_pos - 1):
                self.good_direction = not self.good_direction
            self.prev_column_pos = self.column_pos
            self.column_pos -= 1
            self.dir = 'left'


import gc
import os
import pandas as pd
import pickle

out = pd.DataFrame(columns=["alpha", "beta", "metastability", "chimera"])

tmax = 4000
N = 100*tmax

metadata = pd.read_excel("../connectomes/mouse_meta.xlsx", sheet_name=None)

m = metadata["Voxel Count_295 Structures"]
m = m.loc[m["Represented in Linear Model Matrix"] == "Yes"]

columns = []
cortices = [[0, 0]]
for region in m["Major Region"].unique():
    i = [columns.append(acronym.replace(" ", "")) for acronym in
         m.loc[m["Major Region"] == region, "Acronym"].values]
    cortices.append([cortices[-1][-1], cortices[-1][-1] + len(i)])
cortices.remove([0, 0])
del(metadata)
del(m)

for i in os.listdir("/users/h/m/hmmitche/thesis/data/zoom"):
    print(i)
    with open(f"/users/h/m/hmmitche/thesis/data/zoom/{i}", "rb") as f:
        params, sol, phase, χ, m = pickle.load(f)
    α, β = params[8], params[10]
    vals = sol.y.T.reshape(N, 3, -1)
    end = {"alpha": α,
           "beta": β,
           "max_phase": phase.max(),
           "chimera": χ,
           "metastability": m}
    out = out.append(end, ignore_index=True)
    gc.collect()

with open("../../data/zoom/hizanidis_params.pkl", "wb") as f:
    pickle.dump(out, f)


import sys, os
import h5py
import numpy as np
from datetime import datetime
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
#from keras.layers import Convolution2D, MaxPooling2D, ZeroPadding2D, SpatialDropout2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras.optimizers import SGD
from keras.callbacks import ModelCheckpoint, EarlyStopping, TensorBoard, ReduceLROnPlateau, CSVLogger
#from keras.layers import BatchNormalization

from keras.applications.vgg16 import VGG16

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

basedir = './'
train_data_dir = basedir+'/train'
validation_data_dir = basedir+'/validate'
os.makedirs(basedir+'/output/models', exist_ok=True)
os.makedirs(basedir+'/output/tensorboard', exist_ok=True)
os.makedirs(basedir+'/output/checkpoints', exist_ok=True)
os.makedirs(basedir+'/output/augmented', exist_ok=True)
nb_train_samples = 1881
nb_train_class0 = 903         #benign
nb_train_class1 = 978         #malignant
nb_validation_samples = 481
nb_validation_class0 = 238    #benign
nb_validation_class1 = 243    #malignant

nb_worker = 8  #cpus for real-time image augmentation
batch_size = 32
nb_epoch = 100
img_width, img_height = 224, 224  # target size of input (resizes pictures to this)
modelname = 'VGG16notop'

def save_bottlebeck_features():

    # load the VGG16 network with ImageNet weights
    model = VGG16(weights='imagenet', include_top=False)
    print('Model loaded.')

    datagen = ImageDataGenerator(rescale=1./65535)
    generator = datagen.flow_from_directory(
        train_data_dir,
        target_size=(img_width, img_height),
        batch_size=batch_size,
        class_mode=None, # predict method doesn't accept labels
        shuffle=False)   # our data will be in order 903 benign, 978 malignant
    bottleneck_features_train = model.predict_generator(generator, nb_train_samples)
    np.save(basedir+'/output/checkpoints/'+modelname+'_bottleneck_features_train.npy', bottleneck_features_train)
    print('train bottleneck features saved, shape:', bottleneck_features_train.shape)

    generator = datagen.flow_from_directory(
        validation_data_dir,
        target_size=(img_width, img_height),
        batch_size=batch_size,
        class_mode=None,  # predict method doesn't accept labels
        shuffle=False)    # our data will be in order 238 benign, 243 malignant
    bottleneck_features_validation = model.predict_generator(generator, nb_validation_samples)
    np.save(basedir+'/output/checkpoints/'+modelname+'_bottleneck_features_validation.npy', bottleneck_features_validation)
    print('validation bottleneck features saved, shape:', bottleneck_features_train.shape)

def train_top_model():
    train_data = np.load(basedir+'/output/checkpoints/'+modelname+'_bottleneck_features_train.npy')
    train_labels = np.array( [0]*nb_train_class0 + [1]*nb_train_class1 )

    validation_data = np.load(basedir+'/output/checkpoints/'+modelname+'_bottleneck_features_validation.npy')
    validation_labels = np.array( [0]*nb_validation_class0  + [1]*nb_validation_class1 )

    model = Sequential()
    model.add(Flatten(input_shape=train_data.shape[1:]))
    model.add(Dense(256, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(1, activation='sigmoid'))

    model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])

    #Call backs
    csvlogger = CSVLogger(basedir+'/output/'+modelname+'_top_weights.csv', separator=',', append=True)

    model.fit(train_data, train_labels,
        nb_epoch=nb_epoch,
        batch_size=batch_size,
        validation_data=(validation_data, validation_labels),
        callbacks=[csvlogger])

    model.save_weights(basedir+'/output/checkpoints/'+modelname+'_top_weights.hdf5')

### MAIN
save_bottlebeck_features()
train_top_model()


import json
import re
from functools import partial
from pathlib import Path

from bs4 import BeautifulSoup
from pipe import traverse, select, sort

from real_estate_scraper.parsing import str_from_tag
from real_estate_scraper.utils import compose_functions

from real_estate_scraper.configuration import ScraperConfig
from real_estate_scraper.scraper import Scraper

config_path = Path(__file__).parent / "funda_config.json"
funda_config = ScraperConfig.from_json(config_path)

def extract_number_of_rooms(soup: BeautifulSoup) -> BeautifulSoup:
    result = soup.find("ul", class_="search-result-kenmerken").find_all("li")
    if len(result) > 1:
        return result[1]

def get_href(soup: BeautifulSoup) -> str:
    href = soup.find("a", attrs={"data-object-url-tracking": "resultlist"}).get("href")
    return funda_config.website_settings.main_url + str_from_tag(href)

house_attrs_sh_func_map = {
    "Address": lambda soup: soup.find("h2"),
    "PostCode": lambda soup: soup.find("h4"),
    "LivingArea": lambda soup: soup.find(attrs={"title": "Living area"}),
    "PlotSize": lambda soup: soup.find(attrs={"title": "Plot size"}),
    "Price": lambda soup: soup.find("span", class_="search-result-price"),
    "Rooms": extract_number_of_rooms,
    "href": get_href,
    "HouseId": lambda soup: soup.find(
        "a", attrs={"data-object-url-tracking": "resultlist"}
    ).get("data-search-result-item-anchor"),
}

for item in funda_config.house_items_shallow:
    item.retrieve = compose_functions(str_from_tag, house_attrs_sh_func_map[item.name])

def get_attribute_deep(soup, text_in_website):
    dt = soup.find(
        lambda tag: tag.name == "dt" and text_in_website.lower() in tag.text.lower()
    )
    if dt:
        return list(dt.find_next("dd").stripped_strings)[0]
    else:
        return None

for item in funda_config.house_items_deep:
    if item.name not in ["Description", "Neighbourhood"]:
        func = partial(get_attribute_deep, text_in_website=item.text_in_website)
        item.retrieve = compose_functions(str_from_tag, func)

get_neighbourhood = lambda soup: soup.find("span", class_="object-header__subtitle")
get_description = lambda soup: soup.find("div", class_="object-description-body")

funda_config.house_items_deep.Neighbourhood.retrieve = compose_functions(str_from_tag,
                                                                         get_neighbourhood)
funda_config.house_items_deep.Description.retrieve = compose_functions(str_from_tag,
                                                                       get_description)

def fetch_location(soup: BeautifulSoup) -> dict:
    text = str_from_tag(soup.find_all("script", attrs={"type": "application/json"})[-1])
    latitude, longitude = None, None
    if text:
        loc_dict = json.loads(text)
        latitude, longitude = loc_dict['lat'], loc_dict['lng']
    return latitude, longitude

funda_config.house_items_deep.Latitude.retrieve = lambda soup: fetch_location(soup)[0]
funda_config.house_items_deep.Longitude.retrieve = lambda soup: fetch_location(soup)[1]

def get_max_num_pages(soup: BeautifulSoup) -> int:
    pp_soup = soup.find("div", class_="pagination-pages")
    num_pages = list(
        pp_soup
        | select(lambda x: re.findall("\d+", x.text.replace(",", "")))
        | traverse
        | select(lambda x: int(x))
        | sort(reverse=True)
    )[0]
    return num_pages

def get_num_listings(soup: BeautifulSoup) -> int:
    listings_results = soup.find_all("script", type="application/ld+json")[2].text
    return json.loads(listings_results)["results_total"]

search_results_func_map = {
    "number_of_pages": get_max_num_pages,
    "number_of_listings": get_num_listings,
    "listings": lambda soup: soup.find_all("div", class_="search-result-content-inner"),
}

for item in funda_config.search_results_items:
    item.retrieve = compose_functions(str_from_tag, search_results_func_map[item.name])

def get_funda_scraper(logger, **kwargs):
    return Scraper(config=funda_config, logger=logger, **kwargs)

if __name__ == "__main__":
    scraper = Scraper(config=funda_config)


'''
邻域对比图协同滤波(NCGCF)的TensorFlow实现
    作者：山东大学 数学学院 陈彦丞 (chenyancheng22@mails.ucas.ac.cn)
    指导教师：山东大学数据科学研究院 曲存全
    校外指导教师：中国科学院大学数学科学学院 姜志鹏
    时间：2023.3.28
'''

import tensorflow.compat.v1 as tf

tf.get_logger().setLevel('ERROR') # 不显示ERROR

tf.disable_v2_behavior() # 将TensorFlow 1.x和2.x之间所有不同的全局行为切换为预定的1.x行为

import os
import sys

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' # 屏蔽INFO+WARNING，输出ERROR+FATAL

from NCGCF_methods.addition import *
from NCGCF_methods.batch_test import *
from SN_NCL import sn_ncl, sn_ncl_negative_sample
from SN_CCL import sn_ccl_negative_sample
from clustering import sn_ccl_k_means

class NCGCF(object):
    def __init__(self, data_config, pretrain_data):
        # 参数设置
        self.model_type = 'ncgcf' # 模型类型
        self.adj_type = args.adj_type # 邻接矩阵(拉普拉斯矩阵)的类型
        self.alg_type = args.alg_type # 图卷积层选择(图滤波)

        self.pretrain_data = pretrain_data # 预训练数据

        self.n_users = data_config['n_users'] # 用户数量
        self.n_items = data_config['n_items'] # 项目数量

        self.n_fold = 100 # 与dropout有关?

        self.norm_adj = data_config['norm_adj'] # 邻接矩阵(npz格式)
        self.n_nonzero_elems = self.norm_adj.count_nonzero() # 非零元素的数量

        self.lr = args.lr # 学习率

        self.emb_dim = args.embed_size # 嵌入向量大小
        self.batch_size = args.batch_size # 批量大小

        self.weight_size = eval(args.layer_size) # 每层输出的大小？(参数为一个列表)
        self.n_layers = len(self.weight_size) # 层数 2
        self.sn_ncl_k = args.sn_ncl_k # 结构对比所用偶数层 2

        self.model_type += '_%s_%s_l%d' % (self.adj_type, self.alg_type, self.n_layers) # 模型类型

        self.regs = eval(args.regs) # 正则项系数
        self.decay = self.regs[0] # 衰减系数

        self.SN_NCL = args.SN_NCL # 是否使用SN-NCL损失
        self.weight_sn_ncl = args.weight_sn_ncl # SN-NCL损失系数
        self.sn_ncl_i = args.sn_ncl_i # 调节用户和项目损失比例的系数
        self.sn_ncl_beta = args.sn_ncl_beta # 调节负采样中正负例的权重
        self.sn_ncl_neg_num = args.sn_ncl_neg_num # 负采样中负例的数量

        self.SN_CCL = args.SN_CCL # 是否使用SN-CCL损失
        self.k_user = args.k_user # 用户的聚类类别数量
        self.k_item = args.k_item  # 项目的聚类类别数量
        self.weight_sn_ccl = args.weight_sn_ccl # SN-CCL损失系数
        self.sn_ccl_i = args.sn_ccl_i # 调节用户和项目损失比例的系数
        self.sn_ccl_beta = args.sn_ccl_beta  # 调节负采样中正负例的权重
        self.sn_ccl_neg_num = args.sn_ccl_neg_num  # 负采样中负例的数量

        self.verbose = args.verbose # 评估间隔

        tf.reset_default_graph() # 重置图保证能正常导入保存的参数
        '''
        *********************************************************
        为输入数据创建占位符 & Dropout设置。
        '''
        # 占位符定义
        self.users = tf.placeholder(tf.int32, shape=(None,)) # 此函数可以理解为形参，用于定义过程，在执行的时候再赋具体的值
        self.items = tf.placeholder(tf.int32, shape=(None,))
        self.pos_items = tf.placeholder(tf.int32, shape=(None,))
        self.neg_items = tf.placeholder(tf.int32, shape=(None,))

        # dropout
        self.node_dropout_flag = args.node_dropout_flag  # 用'node_dropout_flag'指示是否使用该技术
        self.node_dropout = tf.placeholder(tf.float32, shape=[None]) # 点的dropout系数
        self.mess_dropout = tf.placeholder(tf.float32, shape=[None]) # 消息的dropout系数

        """
        *********************************************************
        设置模型参数 (i.e., 初始化权重)。
        """
        # 模型参数初始化
        self.weights = self._init_weights()

        """
        *********************************************************
        通过图神经网络的消息传递机制计算所有用户和项目的基于图的表示。
        图滤波器及来源:
            1. ncgcf: defined in '基于图神经网络的推荐系统——邻域对比图协同滤波'
            2. lightgcn: defined in 'LightGCN: Simplifying and Powering Graph Convolution Network for Recommendation', SIGIR2020;
            3. ngcf: defined in 'Neural Graph Collaborative Filtering', SIGIR2019;
            4. gcn:  defined in 'Semi-Supervised Classification with Graph Convolutional Networks', ICLR2018;
            5. gcmc: defined in 'Graph Convolutional Matrix Completion', KDD2018;
        """

        if self.alg_type in ['ncgcf']:
            self.ua_embeddings, self.ia_embeddings, self.all_u_embeddings_k, self.all_i_embeddings_k = self._create_ncgcf_embed()

        elif self.alg_type in ['lightgcn']:
            self.ua_embeddings, self.ia_embeddings = self._create_lightgcn_embed()

        elif self.alg_type in ['ngcf']:
            self.ua_embeddings, self.ia_embeddings = self._create_ngcf_embed()

        elif self.alg_type in ['gcn']:
            self.ua_embeddings, self.ia_embeddings = self._create_gcn_embed()

        elif self.alg_type in ['gcmc']:
            self.ua_embeddings, self.ia_embeddings = self._create_gcmc_embed()

        """
        *********************************************************
        批量建立用户-项目对的最终表示形式。
        """
        self.u_g_embeddings = tf.nn.embedding_lookup(self.ua_embeddings, self.users) # self.users为小批次训练采样标签
        self.i_g_embeddings = tf.nn.embedding_lookup(self.ia_embeddings, self.items)
        self.pos_i_g_embeddings = tf.nn.embedding_lookup(self.ia_embeddings, self.pos_items)
        self.neg_i_g_embeddings = tf.nn.embedding_lookup(self.ia_embeddings, self.neg_items)

        if self.SN_NCL:
            self.u_embeddings_k = tf.nn.embedding_lookup(self.all_u_embeddings_k, self.users)  # 对应于小批次随机抽样的k阶嵌入向量
            self.i_embeddings_k = tf.nn.embedding_lookup(self.all_i_embeddings_k, self.items)

        self.u_embeddings_0 = tf.nn.embedding_lookup(self.weights['user_embedding'], self.users) # 对应于小批次随机抽样的初始嵌入向量
        self.i_embeddings_0 = tf.nn.embedding_lookup(self.weights['item_embedding'], self.items)
        self.pos_i_embeddings_0 = tf.nn.embedding_lookup(self.weights['item_embedding'], self.pos_items)
        self.neg_i_embeddings_0 = tf.nn.embedding_lookup(self.weights['item_embedding'], self.neg_items)
        # tf.nn.embedding_lookup( params, ids, partition_strategy='mod', name=None, validate_indices=True, max_norm=None)
        # tf.nn.embedding_lookup( params, ids, partition_strategy='mod', name=None, validate_indices=True, max_norm=None)
        # 该函数作用是找到要寻找的embedding data中的对应的行下的vector
        # params：由一个tensor或者多个tensor组成的列表(多个tensor组成时，每个tensor除了第一个维度其他维度需相等)；
        # ids：一个类型为int32或int64的Tensor，包含要在params中查找的id；
        # 返回值是一个dense tensor，返回的shape为shape(ids)+shape(params)[1:]

        self.batch_ratings = tf.matmul(self.u_g_embeddings, self.pos_i_g_embeddings, transpose_a=False,
                                       transpose_b=True) # 用户-项目对的最终表示形式 y* = e_u^T * e_i

        """
        *********************************************************
        生成预测 & 通过BPR损失、SN-NCL损失和SN-CCL损失进行优化.
        """
        # BPR loss
        self.mf_loss, self.emb_loss, self.reg_loss = self.create_bpr_loss(self.u_g_embeddings,
                                                                          self.pos_i_g_embeddings,
                                                                          self.neg_i_g_embeddings)
        self.BPR_loss = self.mf_loss + self.emb_loss + self.reg_loss # 推荐损失+嵌入损失+正则损失

        # SN-NCL loss
        self.sn_ncl_loss = tf.constant(0.0, tf.float32, [1])
        if self.SN_NCL:
            # self.sn_ncl_loss = sn_ncl(self.u_embeddings_k, self.i_embeddings_k,
            #                           self.u_embeddings_0, self.i_embeddings_0, self.sn_ncl_i)
            self.sn_ncl_loss = sn_ncl_negative_sample(self.u_embeddings_k, self.i_embeddings_k, self.u_embeddings_0,
                                                      self.i_embeddings_0, self.sn_ncl_i, self.sn_ncl_beta, self.sn_ncl_neg_num)
            self.sn_ncl_loss = self.weight_sn_ncl * self.sn_ncl_loss

        # SN-CCL loss
        self.sn_ccl_loss = tf.constant(0.0, tf.float32, [1])
        if self.SN_CCL:
            self.all_u_latent_prototype = sn_ccl_k_means(self.weights['user_embedding'], self.k_user)
            self.all_i_latent_prototype = sn_ccl_k_means(self.weights['item_embedding'], self.k_item)

            self.u_latent_prototype = tf.nn.embedding_lookup(self.all_u_latent_prototype, self.users)
            self.i_latent_prototype = tf.nn.embedding_lookup(self.all_i_latent_prototype, self.items)

            self.sn_ccl_loss = sn_ccl_negative_sample(self.u_g_embeddings, self.i_g_embeddings, self.u_latent_prototype,
                                                      self.i_latent_prototype, self.sn_ccl_i, self.sn_ccl_beta, self.sn_ccl_neg_num)
            self.sn_ccl_loss = self.weight_sn_ccl * self.sn_ccl_loss

        self.loss = self.BPR_loss + self.sn_ncl_loss + self.sn_ccl_loss

        self.opt = tf.train.AdamOptimizer(learning_rate=self.lr).minimize(self.loss) # Adam优化器
        # tf.train.AdamOptimizer()除了利用反向传播算法对权重和偏置项进行修正外，也在运行中不断修正学习率。
        # 本质上是带有动量项的RMSprop，它利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。
        # Adam的优点主要在于经过偏置校正后，每一次迭代学习率都有个确定范围，使得参数比较平稳。

    """
    *********************************************************
    初始化权重函数。
    """
    def _init_weights(self):
        all_weights = dict() # 建立空字典

        initializer = tf.keras.initializers.glorot_normal()
        # Glorot正态分布初始化器，也称为Xavier正态分布初始化器。
        # 它从以0为中心，标准差为stddev = sqrt(2 / (fan_in + fan_out))的截断正态分布中抽取样本，
        # 其中fan_in是权值张量中的输入单位的数量，fan_out是权值张量中的输出单位的数量。

        if self.pretrain_data is None: # 是否有预训练数据
            all_weights['user_embedding'] = tf.Variable(initializer([self.n_users, self.emb_dim]), # 初始器参数是形状
                                                        name='user_embedding') # 用户嵌入的参数
            all_weights['item_embedding'] = tf.Variable(initializer([self.n_items, self.emb_dim]),
                                                        name='item_embedding') # 项目嵌入的参数
            # tf.Variable()函数用于创建变量(Variable), 变量是一个特殊的张量，其可以是任意的形状和类型的张量。
            print('using xavier initialization') # 随机初始化
        else:
            all_weights['user_embedding'] = tf.Variable(initial_value=self.pretrain_data['user_embed'], trainable=True,
                                                        name='user_embedding', dtype=tf.float32)
            all_weights['item_embedding'] = tf.Variable(initial_value=self.pretrain_data['item_embed'], trainable=True,
                                                        name='item_embedding', dtype=tf.float32)
            print('using pretrained initialization') # 预训练初始化

        self.weight_size_list = [self.emb_dim] + self.weight_size # 权重size的列表[64,64]/[输入,输出]

        for k in range(self.n_layers): # 消息聚合参数初始化 self.n_layers = 1，k = 0
            all_weights['W_gc_%d' % k] = tf.Variable(
                initializer([self.weight_size_list[k], self.weight_size_list[k + 1]]), name='W_gc_%d' % k)
            all_weights['b_gc_%d' % k] = tf.Variable(
                initializer([1, self.weight_size_list[k + 1]]), name='b_gc_%d' % k)

            all_weights['W_bi_%d' % k] = tf.Variable(
                initializer([self.weight_size_list[k], self.weight_size_list[k + 1]]), name='W_bi_%d' % k)
            all_weights['b_bi_%d' % k] = tf.Variable(
                initializer([1, self.weight_size_list[k + 1]]), name='b_bi_%d' % k)

            all_weights['W_mlp_%d' % k] = tf.Variable(
                initializer([self.weight_size_list[k], self.weight_size_list[k + 1]]), name='W_mlp_%d' % k)
            all_weights['b_mlp_%d' % k] = tf.Variable(
                initializer([1, self.weight_size_list[k + 1]]), name='b_mlp_%d' % k)

        return all_weights

    """
    *********************************************************
    矩阵分割函数。
    """
    def _split_A_hat(self, X): # 输入X是矩阵(压缩稀疏行格式，csr_matrix)
        A_fold_hat = []

        fold_len = (self.n_users + self.n_items) // self.n_fold # 分块数量
        for i_fold in range(self.n_fold): # 相当于按照n_fold大小进行切块
            start = i_fold * fold_len
            if i_fold == self.n_fold - 1:
                end = self.n_users + self.n_items
            else:
                end = (i_fold + 1) * fold_len

            A_fold_hat.append(self._convert_sp_mat_to_sp_tensor(X[start:end])) # 将切块后的矩阵(仍是csr_matrix)转化为稀疏张量加入列表
        return A_fold_hat # 返回列表

    def _split_A_hat_node_dropout(self, X): # 带有点dropout的矩阵分割函数
        A_fold_hat = []

        fold_len = (self.n_users + self.n_items) // self.n_fold
        for i_fold in range(self.n_fold):
            start = i_fold * fold_len
            if i_fold == self.n_fold - 1:
                end = self.n_users + self.n_items
            else:
                end = (i_fold + 1) * fold_len

            # A_fold_hat.append(self._convert_sp_mat_to_sp_tensor(X[start:end]))
            temp = self._convert_sp_mat_to_sp_tensor(X[start:end])
            n_nonzero_temp = X[start:end].count_nonzero() # 计算非零元素个数
            A_fold_hat.append(self._dropout_sparse(temp, 1 - self.node_dropout[0], n_nonzero_temp))

        return A_fold_hat

    """
    *********************************************************
    图滤波器函数。
    """

    def _create_ncgcf_embed(self):
        # L = D^(-1/2)AD^(-1/2)
        if self.node_dropout_flag:
            A_fold_hat = self._split_A_hat_node_dropout(self.norm_adj)
        else:
            A_fold_hat = self._split_A_hat(self.norm_adj)

        # E^(0) = [eu_0^(0),...,eu_M^(0), ei_0^(0),...ei_N^(0)]
        ego_embeddings = tf.concat([self.weights['user_embedding'], self.weights['item_embedding']], axis=0)
        all_embeddings = [ego_embeddings]

        # [E^(0),E^(1),...,E^(n_layers)]
        for k in range(0, self.n_layers):
            temp_embed = []
            # E^(k + 1) = L * E^(k)
            for f in range(self.n_fold):
                temp_embed.append(tf.sparse_tensor_dense_matmul(A_fold_hat[f], ego_embeddings))
            ego_embeddings = tf.concat(temp_embed, 0)

            all_u_embeddings_k = None
            all_i_embeddings_k = None
            if self.SN_NCL == 1 and k == self.sn_ncl_k - 1: # 保存k阶邻居消息的聚合后的节点表示
                all_embeddings_k = ego_embeddings
                all_u_embeddings_k, all_i_embeddings_k = tf.split(all_embeddings_k, [self.n_users, self.n_items], 0)

            all_embeddings += [ego_embeddings]

        # [[eu_0^(0),eu_0^(1),...,eu_0^(n_layers)],...,[ei_N^(0),ei_N^(1),...,ei_N^(n_layers)]]
        all_embeddings = tf.stack(all_embeddings, 1)  # 张量堆叠，此时eu_0^(0),eu_0^(1)分别是独立的张量
        all_embeddings = tf.reduce_mean(all_embeddings, axis=1, keepdims=False)  # 最终节点表示，可改为加权求和
        u_g_embeddings, i_g_embeddings = tf.split(all_embeddings, [self.n_users, self.n_items], 0)
        return u_g_embeddings, i_g_embeddings, all_u_embeddings_k, all_i_embeddings_k

    def _create_lightgcn_embed(self):
        # L = D^(-1/2)AD^(-1/2)
        if self.node_dropout_flag:
            A_fold_hat = self._split_A_hat_node_dropout(self.norm_adj)
        else:
            A_fold_hat = self._split_A_hat(self.norm_adj)

        # E^(0) = [eu_0^(0),...,eu_M^(0), ei_0^(0),...ei_N^(0)]
        ego_embeddings = tf.concat([self.weights['user_embedding'], self.weights['item_embedding']], axis=0)
        all_embeddings = [ego_embeddings]

        # [E^(0),E^(1),...,E^(n_layers)]
        for k in range(0, self.n_layers):
            temp_embed = []
            # E^(k + 1) = L * E^(k)
            for f in range(self.n_fold):
                temp_embed.append(tf.sparse_tensor_dense_matmul(A_fold_hat[f], ego_embeddings))
            ego_embeddings = tf.concat(temp_embed, 0)
            all_embeddings += [ego_embeddings]

        # [[eu_0^(0),eu_0^(1),...,eu_0^(n_layers)],...,[ei_N^(0),ei_N^(1),...,ei_N^(n_layers)]]
        all_embeddings = tf.stack(all_embeddings, 1) # 张量堆叠，此时eu_0^(0),eu_0^(1)分别是独立的张量
        all_embeddings = tf.reduce_mean(all_embeddings, axis=1, keepdims=False) # 最终节点表示，可改为加权求和
        u_g_embeddings, i_g_embeddings = tf.split(all_embeddings, [self.n_users, self.n_items], 0)
        return u_g_embeddings, i_g_embeddings

    def _create_ngcf_embed(self): # NGCF
        # L = D^(-1/2)AD^(-1/2)
        if self.node_dropout_flag:
            A_fold_hat = self._split_A_hat_node_dropout(self.norm_adj)
        else:
            A_fold_hat = self._split_A_hat(self.norm_adj)

        # E^(0) = [eu_0^(0),...,eu_M^(0), ei_0^(0),...ei_N^(0)]
        ego_embeddings = tf.concat([self.weights['user_embedding'], self.weights['item_embedding']], axis=0)
        all_embeddings = [ego_embeddings]

        # [E^(0),E^(1),...,E^(n_layers)]
        for k in range(0, self.n_layers):
            temp_embed = []
            # L * E^(k)
            for f in range(self.n_fold):
                temp_embed.append(tf.sparse_tensor_dense_matmul(A_fold_hat[f], ego_embeddings))
            side_embeddings = tf.concat(temp_embed, 0)

            # LeakyReLU(W_1 * (L * E^(k)) + b_1)
            sum_embeddings = tf.nn.leaky_relu(
                tf.matmul(side_embeddings, self.weights['W_gc_%d' % k]) + self.weights['b_gc_%d' % k])

            # E^(k) ⊙ (L * E^(k))
            bi_embeddings = tf.multiply(ego_embeddings, side_embeddings)

            # LeakyReLU(W_2 * (E^(k) ⊙ (L * E^(k))) + b_2)
            bi_embeddings = tf.nn.leaky_relu(
                tf.matmul(bi_embeddings, self.weights['W_bi_%d' % k]) + self.weights['b_bi_%d' % k])

            # E^(k + 1) = LeakyReLU(W_1 * (L * E^(k)) + b_1) + LeakyReLU(W_2 * (E^(k) ⊙ (L * E^(k))) + b_2)
            ego_embeddings = sum_embeddings + bi_embeddings
            # ego_embeddings = tf.nn.dropout(ego_embeddings, 1 - self.mess_dropout[k]) # dropout
            norm_embeddings = tf.math.l2_normalize(ego_embeddings, axis=1) # l2正则化
            all_embeddings += [norm_embeddings]

        # [[eu_0^(0)-eu_0^(1)-...-eu_0^(n_layers)],...,[ei_N^(0)-ei_N^(1)-...-ei_N^(n_layers)]]
        all_embeddings = tf.concat(all_embeddings, 1) # tf.concat()张量拼接，此时[eu_0^(0)-eu_0^(1)-...-eu_0^(n_layers)]是一个张量
        u_g_embeddings, i_g_embeddings = tf.split(all_embeddings, [self.n_users, self.n_items], 0)
        return u_g_embeddings, i_g_embeddings

    def _create_gcn_embed(self):
        # L = (D+I)^(-1/2)(A+I)(D+I)^(-1/2)
        A_fold_hat = self._split_A_hat(self.norm_adj)

        # E^(0) = [eu_0^(0),...,eu_M^(0), ei_0^(0),...ei_N^(0)]
        embeddings = tf.concat([self.weights['user_embedding'], self.weights['item_embedding']], axis=0)
        all_embeddings = [embeddings]

        # [E^(0),E^(1),...,E^(n_layers)]
        for k in range(0, self.n_layers):
            temp_embed = []
            # L * E^(k)
            for f in range(self.n_fold):
                temp_embed.append(tf.sparse_tensor_dense_matmul(A_fold_hat[f], embeddings))
            embeddings = tf.concat(temp_embed, 0)

            # LeakyReLU(W * (L * E^(k)) + b)
            embeddings = tf.nn.leaky_relu(
                tf.matmul(embeddings, self.weights['W_gc_%d' % k]) + self.weights['b_gc_%d' % k])
            # embeddings = tf.nn.dropout(embeddings, 1 - self.mess_dropout[k]) # dropout
            all_embeddings += [embeddings]

        # [[eu_0^(0)-eu_0^(1)-...-eu_0^(n_layers)],...,[ei_N^(0)-ei_N^(1)-...-ei_N^(n_layers)]]
        all_embeddings = tf.concat(all_embeddings, 1)
        u_g_embeddings, i_g_embeddings = tf.split(all_embeddings, [self.n_users, self.n_items], 0)
        return u_g_embeddings, i_g_embeddings

    def _create_gcmc_embed(self):
        # L = D^(-1)A
        A_fold_hat = self._split_A_hat(self.norm_adj)

        # E^(0) = [eu_0^(0),...,eu_M^(0), ei_0^(0),...ei_N^(0)]
        embeddings = tf.concat([self.weights['user_embedding'], self.weights['item_embedding']], axis=0)
        all_embeddings = []

        # [E^(1),...,E^(n_layers)]
        for k in range(0, self.n_layers):
            # L * E^(k)
            temp_embed = []
            for f in range(self.n_fold):
                temp_embed.append(tf.sparse_tensor_dense_matmul(A_fold_hat[f], embeddings))
            embeddings = tf.concat(temp_embed, 0)
            # LeakyReLU(W * (L * E^(k)) + b)
            embeddings = tf.nn.leaky_relu(
                tf.matmul(embeddings, self.weights['W_gc_%d' % k]) + self.weights['b_gc_%d' % k])

            # W_mlp * (LeakyReLU(W * (L * E^(k)) + b)) + b_mlp
            mlp_embeddings = tf.matmul(embeddings, self.weights['W_mlp_%d' % k]) + self.weights['b_mlp_%d' % k]
            # mlp_embeddings = tf.nn.dropout(mlp_embeddings, 1 - self.mess_dropout[k]) # dropout
            all_embeddings += [mlp_embeddings]

        # [[eu_0^(1)-eu_0^(2)-...-eu_0^(n_layers)],...,[ei_N^(1)-ei_N^(2)-...-ei_N^(n_layers)]]
        all_embeddings = tf.concat(all_embeddings, 1)

        u_g_embeddings, i_g_embeddings = tf.split(all_embeddings, [self.n_users, self.n_items], 0)
        return u_g_embeddings, i_g_embeddings

    def create_bpr_loss(self, users, pos_items, neg_items):
        pos_scores = tf.reduce_sum(tf.multiply(users, pos_items), axis=1) # tf.multiply()逐元素相乘
        neg_scores = tf.reduce_sum(tf.multiply(users, neg_items), axis=1) # axis=1按列求和

        # 节点嵌入表示的L2正则化，张量中的每一个元素进行平方，然后求和，最后乘一个1/2
        regularizer_emb = tf.nn.l2_loss(users) + tf.nn.l2_loss(pos_items) + tf.nn.l2_loss(neg_items)
        regularizer_emb = regularizer_emb / self.batch_size

        # -ln(σ(e_u^T * e_i - e_u^T * e_j))
        maxi = tf.log(tf.nn.sigmoid(pos_scores - neg_scores))
        mf_loss = tf.negative(tf.reduce_mean(maxi)) # tf.reduce_mean函数用于计算张量tensor沿着指定的数轴(tensor的某一维度)上的的平均值
        # 另一种表示方法mf_loss = tf.reduce_sum(tf.nn.softplus(-(pos_scores - neg_scores)))
        # Softplus函数可以看作是ReLU函数的平滑，Softplus(x) = ln(1 + e^x)

        emb_loss = self.decay * regularizer_emb # 嵌入表示的正则化损失

        # reg_loss = tf.constant(0.0, tf.float32, [1]) # 参数的正则化损失，默认为常数0
        regularizer_reg = tf.concat([self.u_embeddings_0, self.pos_i_embeddings_0, self.neg_i_embeddings_0], axis=0) # 参数正则化(初始嵌入向量)
        reg_loss = (self.decay * tf.nn.l2_loss(regularizer_reg)) / self.batch_size

        return mf_loss, emb_loss, reg_loss

    """
    *********************************************************
    矩阵张量转化函数。
    """
    def _convert_sp_mat_to_sp_tensor(self, X): # 输入矩阵格式为csr_matrix
        coo = X.tocoo().astype(np.float32) # 将此矩阵转换为坐标格式，coo_matrix
        indices = np.mat([coo.row, coo.col]).transpose() # 用numpy将坐标转化成矩阵并转置
        return tf.SparseTensor(indices, coo.data, coo.shape) # indices:'numpy.matrix', data:'numpy.ndarray', shape:'tuple'
        # 稀疏张量tf.SparseTensor只记录非零元素的位置和数值，可以减少存储空间
        # 非零元素所在位置的索引，非零元素的数值，矩阵维度

    """
    *********************************************************
    稀疏张量dropout函数。
    """
    def _dropout_sparse(self, X, keep_prob, n_nonzero_elems):
        noise_shape = [n_nonzero_elems] # 非零元素个数
        random_tensor = keep_prob # 1-dropout
        random_tensor += tf.random_uniform(noise_shape) # 从一个均匀分布[low,high)中随机采样，默认[0,1)，返回形状为(n_nonzero_elems,)的张量
        dropout_mask = tf.cast(tf.floor(random_tensor), dtype=tf.bool) # tf.cast数据类型转换，tf.floor向下取整
        pre_out = tf.sparse_retain(X, dropout_mask)
        # tf.sparse_retain(sp_input, to_retain)
        # sp_input：输入的SparseTensor带有N个非空元素，to_retain：长度为N的具有M个真值的bool向量
        # 函数返回一个与输入具有相同形状并且有M个非空元素的SparseTensor，它对应于to_retain的真实位置

        return pre_out * tf.div(1., keep_prob) # tf.div点除运算

"""
*********************************************************
加载预训练数据函数。
"""
def load_pretrained_data():
    pretrain_path = '%spretrain/%s/%s.npz' % (args.proj_path, args.dataset, 'embedding')
    try:
        pretrain_data = np.load(pretrain_path)
        print('load the pretrained embeddings.')
    except Exception:
        pretrain_data = None
    return pretrain_data

"""
*********************************************************
NCGCF模型数据及调用。
"""
if __name__ == '__main__':
    # os.environ["CUDA_VISIBLE_DEVICES"] = str(args.gpu_id)

    config = dict() # 创建字典
    config['n_users'] = data_generator.n_users # 用户数量
    config['n_items'] = data_generator.n_items # 项目数量

    data_generator.print_statistics() # 打印数据：用户数，项目数，交互数，稀疏性，训练集，测试集

    """
    *********************************************************
    生成拉普拉斯矩阵，其中每个位置的元素定义两个连接节点之间的衰减因子(例如，p_ui)。
    """
    plain_adj, norm_adj, mean_adj = data_generator.get_adj_mat() # 返回的矩阵为压缩稀疏行格式csr_matrix，重复的条目将被汇总

    if args.adj_type == 'plain': # 常规
        config['norm_adj'] = plain_adj
        print('use the plain adjacency matrix')

    elif args.adj_type == 'norm': # 加单位矩阵后归一化，默认
        config['norm_adj'] = norm_adj
        print('use the normalized adjacency matrix')

    elif args.adj_type == 'mean': # 归一化?
        config['norm_adj'] = mean_adj
        print('use the mean adjacency matrix')

    else:
        config['norm_adj'] = mean_adj + sp.eye(mean_adj.shape[0]) # 归一化后加单位矩阵(自连接)
        print('use the ngcf adjacency matrix')

    t0 = time()

    if args.pretrain == -1:
        pretrain_data = load_pretrained_data() # 加载预训练数据
    else:
        pretrain_data = None

    model = NCGCF(data_config=config, pretrain_data=pretrain_data) # NCGCF
    # 部分数据由上述定义，另一部分在参数包parser.py中定义

    """
    *********************************************************
    保存模型参数。
    """
    saver = tf.train.Saver() # tf.train.Saver()是一个类，提供了变量、模型(也称图Graph)的保存和恢复模型方法
    # TensorFlow是通过构造Graph的方式进行深度学习，任何操作(如卷积、池化等)都需要operator，保存和恢复操作也不例外
    # 在tf.train.Saver()类初始化时，用于保存和恢复的save和restore operator会被加入Graph
    # TensorFlow将变量保存在二进制checkpoint文件中，这类文件会将变量名称映射到张量值。所以，下列类初始化操作应在搭建Graph时完成

    if args.save_flag == 1:
        layer = '-'.join([str(l) for l in eval(args.layer_size)]) # 将每层输出的大小以—为间隔输出为字符串，eval()的输入也是字符串
        weights_save_path = '%sweights/%s/%s/%s/l%s_r%s' % (args.weights_path, args.dataset, model.model_type, layer,
                                                            str(args.lr), '-'.join([str(r) for r in eval(args.regs)]))
        ensureDir(weights_save_path)
        save_saver = tf.train.Saver(max_to_keep=1) # max_to_keep表明保存的最大checkpoint文件数。
        # 当一个新文件创建的时候，旧文件就会被删掉，如果值为None或0，表示保存所有的checkpoint文件

    config = tf.ConfigProto() # tf.ConfigProto()主要的作用是配置tf.Session的运算方式，比如gpu运算或者cpu运算
    # tf.ConfigProto()一般用在创建Session的时候，用来对Session进行参数配置
    config.gpu_options.allow_growth = True # 动态申请显存，GPU容量按需慢慢增加
    sess = tf.Session(config=config)
    # TensorFlow里变量的定义和初始化是分开的，所有关于图变量的赋值和计算都要通过tf.Session的run来进行
    # session用于执行命令，对话控制。sess.run()用于执行某一个小图上的功能

    """
    *********************************************************
    重新加载预训练的模型参数。
    """
    if args.pretrain == 1:
        layer = '-'.join([str(l) for l in eval(args.layer_size)])

        pretrain_path = '%sweights/%s/%s/%s/l%s_r%s' % (args.weights_path, args.dataset, model.model_type, layer,
                                                        str(args.lr), '-'.join([str(r) for r in eval(args.regs)])) # 路径同上

        ckpt = tf.train.get_checkpoint_state(os.path.dirname(pretrain_path + '/checkpoint'))
        # tf.train.get_checkpoint_state：从“检查点”文件返回CheckpointState原型，有model_checkpoint_path和all_model_checkpoint_paths两个属性
        # 其中model_checkpoint_path保存了最新的tensorflow模型文件的路径+文件名，是个字符串。all_model_checkpoint_paths则有未被删除的所有tensorflow模型文件的路径+文件名，是个列表。

        if ckpt and ckpt.model_checkpoint_path:
            sess.run(tf.global_variables_initializer()) # 初始化所有全局变量
            saver.restore(sess, ckpt.model_checkpoint_path) # saver.restore()会根据ckpt.model_checkpoint_path自动寻找参数名—值文件进行加载
            # 基于checkpoint文件(ckpt)加载参数时，实际上就是用Saver.restore取代了initializer的初始化
            print('load the pretrained model parameters from: ', pretrain_path)

            # *********************************************************
            # 呈现预训练模型的结果
            if args.report != 1:
                users_to_test = list(data_generator.test_set.keys()) # 测试集的用户
                ret = test(sess, model, users_to_test, drop_flag=True) # 测试函数，返回一个字典
                cur_best_pre_0 = ret['recall'][0] # 最佳召回率

                pretrain_ret = 'pretrained model recall=[%.5f, %.5f], precision=[%.5f, %.5f], hit=[%.5f, %.5f],' \
                               'ndcg=[%.5f, %.5f]' % \
                               (ret['recall'][0], ret['recall'][-1],
                                ret['precision'][0], ret['precision'][-1],
                                ret['hit_ratio'][0], ret['hit_ratio'][-1],
                                ret['ndcg'][0], ret['ndcg'][-1])
                print(pretrain_ret)
        else:
            sess.run(tf.global_variables_initializer()) # 初始化所有全局变量
            cur_best_pre_0 = 0.
            print('without pretraining.')

    else:
        sess.run(tf.global_variables_initializer()) # 初始化所有全局变量
        cur_best_pre_0 = 0.
        print('without pretraining.')

    """
    *********************************************************
    获得不同稀疏级别的性能表现。
    """
    if args.report == 1:
        assert args.test_flag == 'full'
        users_to_test_list, split_state = data_generator.get_sparsity_split() # 获得稀疏分解，返回的是字符串构成的列表
        users_to_test_list.append(list(data_generator.test_set.keys()))
        split_state.append('all')

        report_path = '%sreport/%s/%s.result' % (args.proj_path, args.dataset, model.model_type) # 路径
        ensureDir(report_path)
        f = open(report_path, 'w')
        f.write(
            'embed_size=%d, lr=%.4f, layer_size=%s, keep_prob=%s, regs=%s, loss_type=%s, adj_type=%s\n'
            % (args.embed_size, args.lr, args.layer_size, args.keep_prob, args.regs, args.loss_type, args.adj_type))

        for i, users_to_test in enumerate(users_to_test_list):
            ret = test(sess, model, users_to_test, drop_flag=True)

            final_perf = "recall=[%s], precision=[%s], hit=[%s], ndcg=[%s]" % \
                         ('\t'.join(['%.5f' % r for r in ret['recall']]),
                          '\t'.join(['%.5f' % r for r in ret['precision']]),
                          '\t'.join(['%.5f' % r for r in ret['hit_ratio']]),
                          '\t'.join(['%.5f' % r for r in ret['ndcg']]))
            print(final_perf)

            f.write('\t%s\n\t%s\n' % (split_state[i], final_perf))
        f.close()
        exit()

    """
    *********************************************************
    训练模型。
    """
    loss_loger, pre_loger, rec_loger, ndcg_loger, hit_loger = [], [], [], [], []
    stopping_step = 0
    should_stop = False

    for epoch in range(args.epoch): # 训练次数
        t1 = time()
        loss, mf_loss, emb_loss, reg_loss, sn_ncl_loss, sn_ccl_loss= 0., 0., 0., 0., 0., 0.
        n_batch = data_generator.n_train // args.batch_size + 1 # 批次的数量

        for idx in range(n_batch): # 按批次进行训练
            users, pos_items, neg_items, items = data_generator.sample() # 采样
            _, batch_loss, batch_mf_loss, batch_emb_loss, batch_reg_loss, batch_sn_ncl_loss, batch_sn_ccl_loss = sess.run(
                [model.opt, model.loss, model.mf_loss, model.emb_loss, model.reg_loss,
                 model.sn_ncl_loss, model.sn_ccl_loss], # 优化器，各种损失loss
                feed_dict={model.users: users, model.items: items, model.pos_items: pos_items,
                           model.node_dropout: eval(args.node_dropout),
                           model.mess_dropout: eval(args.mess_dropout),
                           model.neg_items: neg_items}) # feed_dict用来设置graph的输入值，先前已经定义了占位符，在这里输入数据即可

            loss += batch_loss # 损失要按批次进行叠加
            mf_loss += batch_mf_loss
            emb_loss += batch_emb_loss
            reg_loss += batch_reg_loss
            sn_ncl_loss += batch_sn_ncl_loss
            sn_ccl_loss += batch_sn_ccl_loss

        if np.isnan(loss) == True: # 损失为NAN进行报错
            print('ERROR: loss is nan.')
            sys.exit()

        # 每args.cycle次训练打印测试评估指标
        if (epoch + 1) % args.cycle != 0:
            if args.verbose > 0 and epoch % args.verbose == 0:
                perf_str = 'Epoch %d [%.1fs]: train==[%.5f=%.5f + %.5f + %.5f + %.5f + %.5f]' % (
                    epoch, time() - t1, loss, mf_loss, emb_loss, reg_loss, sn_ncl_loss, sn_ccl_loss)
                print(perf_str) # 根据args.verbose打印运行时间及loss值
            continue

        t2 = time()
        users_to_test = list(data_generator.test_set.keys()) # 测试用户
        ret = test(sess, model, users_to_test, drop_flag=True)

        t3 = time()

        loss_loger.append(loss)
        rec_loger.append(ret['recall']) # 召回率是数组形式，下面同理
        pre_loger.append(ret['precision'])
        ndcg_loger.append(ret['ndcg'])
        hit_loger.append(ret['hit_ratio'])

        if args.verbose > 0:
            perf_str = 'Epoch %d [%.1fs + %.1fs]: train==[%.5f=%.5f + %.5f + %.5f + %.5f + %.5f], recall=[%.5f, %.5f], ' \
                       'precision=[%.5f, %.5f], hit=[%.5f, %.5f], ndcg=[%.5f, %.5f]' % \
                       (epoch, t2 - t1, t3 - t2, loss, mf_loss, emb_loss, reg_loss, sn_ncl_loss, sn_ccl_loss, ret['recall'][0], ret['recall'][-1],
                        ret['precision'][0], ret['precision'][-1], ret['hit_ratio'][0], ret['hit_ratio'][-1],
                        ret['ndcg'][0], ret['ndcg'][-1])
            print(perf_str) # 打印评估指标，即用户在目标项目上的表现y* = e_u^T * e_i，在测试项目中排名最高和最低的数据

        # *********************************************************
        # 以排名最高的召回率为模型训练到最佳的标准，若cur_best_pre_0连续下降3次，则提前停止训练
        cur_best_pre_0, stopping_step, should_stop = early_stopping(ret['recall'][-1], cur_best_pre_0,
                                                                    stopping_step, expected_order='acc', flag_step=3)

        if should_stop == True:
            break

        # *********************************************************
        # 保存用户和项目嵌入进行预训练
        if ret['recall'][-1] == cur_best_pre_0 and args.save_flag == 1:
            save_saver.save(sess, weights_save_path + '/weights', global_step=epoch) # 保存权重参数
            print('save the weights in path: ', weights_save_path)

    recs = np.array(rec_loger)
    pres = np.array(pre_loger)
    ndcgs = np.array(ndcg_loger)
    hit = np.array(hit_loger)

    best_rec_0 = max(recs[:, 0]) # 找到最高的召回率
    idx = list(recs[:, 0]).index(best_rec_0) # 先把每个数组第一个元素提出组成列表，再返回最高召回率的索引

    final_perf = "Best Iter=[%d]@[%.1f]\trecall=[%s], precision=[%s], hit=[%s], ndcg=[%s]" % \
                 (idx, time() - t0, '\t'.join(['%.5f' % r for r in recs[idx]]),
                  '\t'.join(['%.5f' % r for r in pres[idx]]),
                  '\t'.join(['%.5f' % r for r in hit[idx]]),
                  '\t'.join(['%.5f' % r for r in ndcgs[idx]]))
    print(final_perf) # 以最高召回率为基准，打印在相同训练后的其他指标(不一定是最高或最佳)

    save_path = '%soutput/%s/%s.result' % (args.proj_path, args.dataset, model.model_type)
    ensureDir(save_path) # 创建结果文件
    f = open(save_path, 'a') # 打开文件

    f.write(
        'embed_size=%d, lr=%.4f, layer_size=%s, node_dropout=%s, mess_dropout=%s, regs=%s, adj_type=%s\n\t%s\n'
        % (args.embed_size, args.lr, args.layer_size, args.node_dropout, args.mess_dropout, args.regs,
           args.adj_type, final_perf)) # 写入模型参数与最终结果
    f.close() # 关闭文件


import torch
import geoopt
import ot

from pyriemann.utils.mean import mean_riemann

def otda(Xs, Xt, metric="ai", loss="emd", reg=1):
    """
        Xs: Source (n_batch, d, d)
        Xt: Target (m_batch, d, d)
        metric: "ai" or "le"
        loss: "emd" or "sinkhorn"
        reg: regularization for Sinkhorn
    """
    d = Xs.shape[-1]
    device = Xs.device

    if metric == "ai":
        manifold_spd = geoopt.SymmetricPositiveDefinite("AIM")
    elif metric == "le":
        manifold_spd = geoopt.SymmetricPositiveDefinite("LEM")

    a = torch.ones((len(Xs),), device=device, dtype=torch.float64)/len(Xs)
    b = torch.ones((len(Xt),), device=device, dtype=torch.float64)/len(Xt)
    M = manifold_spd.dist(Xs[:,None], Xt[None])**2

    if loss == "emd":
        P = ot.emd(a, b, M)
    elif loss == "sinkhorn":
        P = ot.sinkhorn(a, b, M, reg)

    if metric == "ai":
        cpt = torch.zeros((len(Xs), d, d))
        for i in range(len(Xs)):
            cpt[i] = torch.tensor(mean_riemann(Xt.cpu().numpy(), sample_weight=P[i].cpu().numpy()))
    elif metric == "le":
        log_Xt = geoopt.linalg.sym_logm(Xt)
        cpt = torch.matmul(P, log_Xt[None].reshape(-1, d*d)).reshape(-1,d,d)
        cpt = geoopt.linalg.sym_expm(cpt*len(Xt))

    return cpt


from django import forms
from .models import Contatenos

class ContatenosForm(forms.ModelForm):
    class Meta:
        model = Contatenos
        fields = ['nome', 'email', 'telefone', 'mensagem']

        widgets = {
            'nome': forms.TextInput(attrs={'class': 'email-bt', 'placeholder': 'Nome', 'nome': 'nome'}),
            'email': forms.EmailInput(attrs={'class': 'email-bt', 'placeholder': 'Email', 'nome': 'email'}),
            'telefone': forms.TextInput(attrs={'class': 'email-bt', 'placeholder': 'Telefone', 'nome': 'telefone'}),
            'mensagem': forms.Textarea(attrs={'class': 'massage-bt', 'placeholder': 'Mensgem', 'nome': 'mensagem'}),
        }