# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import re
import logging
from pelican import signals
from pelican.utils import pelican_open
from PIL import Image, ExifTags
from itertools import chain

logger = logging.getLogger(__name__)
queue_resize = dict()
hrefs = None

def initialized(pelican):
    p = os.path.expanduser('~/Pictures')
    from pelican.settings import DEFAULT_CONFIG
    DEFAULT_CONFIG.setdefault('PHOTO_LIBRARY', p)
    DEFAULT_CONFIG.setdefault('PHOTO_GALLERY', (1024, 768, 80))
    DEFAULT_CONFIG.setdefault('PHOTO_ARTICLE', ( 760, 506, 80))
    DEFAULT_CONFIG.setdefault('PHOTO_THUMB',   ( 192, 144, 60))
    if pelican:
        pelican.settings.setdefault('PHOTO_LIBRARY', p)
        pelican.settings.setdefault('PHOTO_GALLERY', (1024, 768, 80))
        pelican.settings.setdefault('PHOTO_ARTICLE', ( 760, 506, 80))
        pelican.settings.setdefault('PHOTO_THUMB',   ( 192, 144, 60))

def read_notes(filename, msg=None):
    notes = {}
    try:
        with pelican_open(filename) as text:
            for line in text.splitlines():
                m = line.split(':', 1)
                if len(m) > 1:
                    pic = m[0].strip()
                    note = m[1].strip()
                    if pic and note:
                        notes[pic] = note
    except:
        if msg:
            logger.warning(msg, filename)
    return notes

def enqueue_resize(orig, resized, spec=(640, 480, 80)):
    global queue_resize
    if resized not in queue_resize:
        queue_resize[resized] = (orig, spec)
    elif queue_resize[resized] != (orig, spec):
        logger.error('photos: resize conflict for {}, {}-{} is not {}-{}',
                     resized,
                     queue_resize[resized][0], queue_resize[resized][1],
                     orig, spec)

def resize_photos(generator, writer):
    print('photos: {} photo resizes to consider.'
          .format(len(queue_resize.items())))
    for resized, what in queue_resize.items():
        resized = os.path.join(generator.output_path, resized)
        orig, spec = what
        if (not os.path.isfile(resized) or
                os.path.getmtime(orig) > os.path.getmtime(resized)):
            logger.info('photos: make photo %s -> %s', orig, resized)
            im = Image.open(orig)
            try:
                exif = im._getexif()
            except Exception:
                exif = None
            if exif:
                for tag, value in exif.items():
                    decoded = ExifTags.TAGS.get(tag, tag)
                    if decoded == 'Orientation':
                        if   value == 3: im = im.rotate(180)
                        elif value == 6: im = im.rotate(270)
                        elif value == 8: im = im.rotate(90)
                        break
            im.thumbnail((spec[0], spec[1]), Image.ANTIALIAS)
            try:
                os.makedirs(os.path.split(resized)[0])
            except:
                pass
            im.save(resized, 'JPEG', quality=spec[2])

def detect_content(content):

    def replacer(m):
        what = m.group('what')
        value = m.group('value')
        origin = m.group('path')

        if what == 'photo':
            if value.startswith('/'):
                value = value[1:]
            path = os.path.join(settings['PHOTO_LIBRARY'], value)
            if not os.path.isfile(path):
                logger.error('photos: No photo %s', path)
            else:
                photo = os.path.splitext(value)[0].lower() + 'a.jpg'
                origin = os.path.join('/photos', photo)
                enqueue_resize(
                    path,
                    os.path.join('photos', photo),
                    settings['PHOTO_ARTICLE'])

        return ''.join((m.group('markup'), m.group('quote'), origin,
                        m.group('quote')))

    global hrefs
    if hrefs is None:
        regex = r"""
            (?P<markup><\s*[^\>]*  # match tag with src and href attr
                (?:href|src)\s*=)

            (?P<quote>["\'])      # require value to be quoted
            (?P<path>{0}(?P<value>.*?))  # the url value
            \2""".format(content.settings['INTRASITE_LINK_REGEX'])
        hrefs = re.compile(regex, re.X)

    if content._content and '{photo}' in content._content:
        settings = content.settings
        content._content = hrefs.sub(replacer, content._content)

def process_gallery_photo(generator, article, gallery):
    if gallery.startswith('/'):
        gallery = gallery[1:]
    dir_gallery = os.path.join(generator.settings['PHOTO_LIBRARY'], gallery)
    if os.path.isdir(dir_gallery):
        logger.info('photos: Gallery detected: %s', gallery)
        dir_photo = os.path.join('photos', gallery.lower())
        dir_thumb = os.path.join('photos', gallery.lower())
        exifs = read_notes(os.path.join(dir_gallery, 'exif.txt'),
                           msg='photos: No EXIF for gallery %s')
        captions = read_notes(os.path.join(dir_gallery, 'captions.txt'))
        article.photo_gallery = []
        for pic in os.listdir(dir_gallery):
            if pic.startswith('.'): continue
            if pic.endswith('.txt'): continue
            photo = os.path.splitext(pic)[0].lower() + '.jpg'
            thumb = os.path.splitext(pic)[0].lower() + 't.jpg'
            article.photo_gallery.append((
                pic,
                os.path.join(dir_photo, photo),
                os.path.join(dir_thumb, thumb),
                exifs.get(pic, ''),
                captions.get(pic, '')))
            enqueue_resize(
                os.path.join(dir_gallery, pic),
                os.path.join(dir_photo, photo),
                generator.settings['PHOTO_GALLERY'])
            enqueue_resize(
                os.path.join(dir_gallery, pic),
                os.path.join(dir_thumb, thumb),
                generator.settings['PHOTO_THUMB'])

def process_gallery_filename(generator, article, gallery):
    if gallery.startswith('/'):
        gallery = gallery[1:]
    else:
        gallery = os.path.join(article.relative_dir, gallery)
    dir_gallery = os.path.join(generator.settings['PHOTO_LIBRARY'], gallery)
    if os.path.isdir(dir_gallery):
        logger.info('photos: Gallery detected: %s', gallery)
        dir_photo = gallery.lower()
        dir_thumb = os.path.join('photos', gallery.lower())
        exifs = read_notes(os.path.join(dir_gallery, 'exif.txt'),
                           msg='photos: No EXIF for gallery %s')
        captions = read_notes(os.path.join(dir_gallery, 'captions.txt'))
        article.photo_gallery = []
        for pic in os.listdir(dir_gallery):
            if pic.startswith('.'): continue
            if pic.endswith('.txt'): continue
            photo = pic.lower()
            thumb = os.path.splitext(pic)[0].lower() + 't.jpg'
            article.photo_gallery.append((
                pic,
                os.path.join(dir_photo, photo),
                os.path.join(dir_thumb, thumb),
                exifs.get(pic, ''),
                captions.get(pic, '')))
            enqueue_resize(
                os.path.join(dir_gallery, pic),
                os.path.join(dir_thumb, thumb),
                generator.settings['PHOTO_THUMB'])

def detect_gallery(generator):
    for article in chain(generator.articles, generator.drafts):
        if 'gallery' in article.metadata:
            gallery = article.metadata.get('gallery')
            if gallery.startswith('{photo}'):
                process_gallery_photo(generator, article, gallery[7:])
            elif gallery.startswith('{filename}'):
                process_gallery_filename(generator, article, gallery[10:])
            elif gallery:
                logger.error('photos: Gallery tag not recognized: %s', gallery)

def process_image_photo(generator, article, image):
    if image.startswith('/'):
        image = image[1:]
    path = os.path.join(generator.settings['PHOTO_LIBRARY'], image)
    if os.path.isfile(path):
        photo = os.path.splitext(image)[0].lower() + 'a.jpg'
        thumb = os.path.splitext(image)[0].lower() + 't.jpg'
        article.photo_image = (
            os.path.basename(image).lower(),
            os.path.join('photos', photo),
            os.path.join('photos', thumb))
        enqueue_resize(
            path,
            os.path.join('photos', photo),
            generator.settings['PHOTO_ARTICLE'])
        enqueue_resize(
            path,
            os.path.join('photos', thumb),
            generator.settings['PHOTO_THUMB'])
    else:
        logger.error('photo: No photo for %s at %s', article.source_path, path)

def process_image_filename(generator, article, image):
    if image.startswith('/'):
        image = image[1:]
    else:
        image = os.path.join(article.relative_dir, image)
    path = os.path.join(generator.path, image)
    if os.path.isfile(path):
        small = os.path.splitext(image)[0].lower() + 't.jpg'
        article.photo_image = (
            os.path.basename(image),
            image.lower(),
            os.path.join('photos', small))
        enqueue_resize(
            path,
            os.path.join('photos', small),
            generator.settings['PHOTO_THUMB'])
    else:
        logger.error('photos: No photo at %s', path)

def detect_image(generator):
    for article in chain(generator.articles, generator.drafts):
        image = article.metadata.get('image', None)
        if image:
            if image.startswith('{photo}'):
                process_image_photo(generator, article, image[7:])
            elif image.startswith('{filename}'):
                process_image_filename(generator, article, image[10:])
            else:
                logger.error('photos: Image tag not recognized: %s', image)

def register():
    signals.initialized.connect(initialized)
    signals.content_object_init.connect(detect_content)
    signals.article_generator_finalized.connect(detect_gallery)
    signals.article_generator_finalized.connect(detect_image)
    signals.article_writer_finalized.connect(resize_photos)


import time

from mock import patch, ANY

from svc.controllers.controller import DepthController

@patch('svc.controllers.controller.calculate_alert')
@patch('svc.controllers.controller.api_requests')
@patch('svc.controllers.controller.get_depth_by_intervals')
@patch('svc.controllers.controller.get_intervals')
class TestController:

    def setup_method(self):
        self.START = time.time()
        self.STOP = time.time()
        self.CONTROLLER = DepthController()

    def test_measure_depth__should_call_get_intervals(self, mock_gpio, mock_depth, mock_request, mock_alert):
        mock_gpio.return_value = (self.START, self.STOP)
        self.CONTROLLER.measure_depth()

        mock_gpio.assert_called()

    def test_measure_depth__should_call_get_depth_by_intervals(self, mock_gpio, mock_depth, mock_request, mock_alert):
        mock_gpio.return_value = (self.START, self.STOP)

        self.CONTROLLER.measure_depth()

        mock_depth.assert_called_with(self.START, self.STOP)

    def test_measure_depth__should_return_depth_calculation(self, mock_gpio, mock_depth, mock_request, mock_alert):
        mock_gpio.return_value = (self.START, self.STOP)
        expected_depth = 1234.332
        mock_depth.return_value = expected_depth

        actual = self.CONTROLLER.measure_depth()

        assert actual == expected_depth

    def test_measure_depth__should_make_api_call_to_save_depth(self, mock_gpio, mock_depth, mock_request, mock_alert):
        expected_depth = 123.45
        mock_gpio.return_value = (self.START, self.STOP)
        mock_depth.return_value = expected_depth

        self.CONTROLLER.measure_depth()

        mock_request.save_current_depth.assert_called_with(None, expected_depth, self.STOP, ANY)

    def test_measure_depth__should_make_api_call_to_save_depth_with_alert(self, mock_gpio, mock_depth, mock_request, mock_alert):
        expected_alert_level = 2
        mock_gpio.return_value = (self.START, self.STOP)
        mock_alert.return_value = expected_alert_level

        self.CONTROLLER.measure_depth()

        mock_request.save_current_depth.assert_called_with(ANY, ANY, ANY, expected_alert_level)

    def test_measure_depth__should_call_alert_validation(self, mock_gpio, mock_depth, mock_requests, mock_alert):
        depth = 123.45
        mock_gpio.return_value = (self.START, self.STOP)
        mock_depth.return_value = depth

        self.CONTROLLER.measure_depth()

        mock_alert.assert_called_with(depth, depth, None)

    def test_measure_depth__should_average_multiple_results_for_alert_validation(self, mock_gpio, mock_depth, mock_request, mock_alert):
        first_depth = 20.0
        second_depth = 10.0
        mock_gpio.return_value = (self.START, self.STOP)
        mock_depth.side_effect = [first_depth, second_depth]

        self.CONTROLLER.measure_depth()
        self.CONTROLLER.measure_depth()

        average_depth = (first_depth + second_depth) / 2
        mock_alert.assert_called_with(second_depth, average_depth, None)

    def test_measure_depth__should_default_to_zero_when_iterated_but_no_depth_measurement(self, mock_gpio, mock_depth, mock_request, mock_alert):
        depth = 0.0
        mock_gpio.return_value = (self.START, self.STOP)
        mock_depth.return_value = depth

        self.CONTROLLER.measure_depth()

        mock_alert.assert_called_with(depth, 0, None)

    def test_save_daily_average__should_call_api_request(self, mock_gpio, mock_depth, mock_request, mock_alert):
        self.CONTROLLER.save_daily_average()

        mock_request.save_daily_average_depth.assert_called_with(self.CONTROLLER.USER_ID, self.CONTROLLER.AVERAGE_DEPTH)

    def test_save_daily_average__should_reset_objects_depth_and_iteration_tallies(self, mock_gpio, mock_depth, mock_requests, mock_alert):
        self.CONTROLLER.ITERATION = 23
        self.CONTROLLER.AVERAGE_DEPTH = 34.12

        self.CONTROLLER.save_daily_average()

        assert self.CONTROLLER.ITERATION == 0
        assert self.CONTROLLER.AVERAGE_DEPTH == 0


import os
import pygame

def get_actor_display_name(actor, truncate=250):
    name = ' '.join(actor.type_id.replace('_', '.').title().split('.')[1:])
    return (name[:truncate - 1] + u'\u2026') if len(name) > truncate else name

# ==============================================================================
# -- HUD -----------------------------------------------------------------------
# ==============================================================================

class HUD(object):
    def __init__(self, width, height, gnss, imu):
        self.imu = imu
        self.gnss = gnss
        self.dim = (width, height)
        font = pygame.font.Font(pygame.font.get_default_font(), 20)
        font_name = 'courier' if os.name == 'nt' else 'mono'
        fonts = [x for x in pygame.font.get_fonts() if font_name in x]
        default_font = 'ubuntumono'
        mono = default_font if default_font in fonts else fonts[0]
        mono = pygame.font.match_font(mono)
        self._font_mono = pygame.font.Font(mono, 12 if os.name == 'nt' else 14)
        self._notifications = FadingText(font, (width, 40), (0, height - 40))
        self.server_fps = 0
        self.frame = 0
        self.simulation_time = 0
        self._show_info = True
        self._info_text = []
        self._server_clock = pygame.time.Clock()

    def on_world_tick(self, timestamp):
        self._server_clock.tick()
        self.server_fps = self._server_clock.get_fps()
        self.frame = timestamp.frame
        self.simulation_time = timestamp.elapsed_seconds

    def tick(self, world, clock, control):
        self._notifications.tick(world, clock)
        if not self._show_info:
            return

        self._info_text = [
            ('Throttle:', control.throttle, 0.0, 1.0),
            ('Steer:', control.steer, -1.0, 1.0),
            ('Brake:', control.brake, 0.0, 1.0),
            ('Reverse:', control.reverse),
            ('Hand brake:', control.handbrake),
            ('Manual:', control.manualgear),
            'Gear:        %s' % {-1: 'R', 0: 'N'}.get(control.gear, control.gear),
            ' '
        ]

        self._info_text += [
            ' ',
            'GNSS ',
            'Latitude: %2.6f' % self.gnss.latitude,
            'Longitude: %2.6f ' % self.gnss.longitude,
            f'Altitude: {self.gnss.altitude:2.6f}',
            '',
            'IMU ',
            u'Compass:% 17.0f\N{DEGREE SIGN} ' % (self.imu.compass),
            'Accelero: (%5.1f,%5.1f,%5.1f)' % (self.imu.accelerometer[0],self.imu.accelerometer[1],self.imu.accelerometer[2]),
            'Gyroscop: (%5.1f,%5.1f,%5.1f)' % (self.imu.gyroscope[0],self.imu.gyroscope[1],self.imu.gyroscope[2]),
            ' ',
             ]

    def toggle_info(self):
        self._show_info = not self._show_info

    def notification(self, text, seconds=2.0):
        self._notifications.set_text(text, seconds=seconds)

    def error(self, text):
        self._notifications.set_text('Error: %s' % text, (255, 0, 0))

    def render(self, display):
        if self._show_info:
            info_surface = pygame.Surface((220, self.dim[1]))
            info_surface.set_alpha(100)
            display.blit(info_surface, (0, 0))
            v_offset = 4
            bar_h_offset = 100
            bar_width = 106
            for item in self._info_text:
                if v_offset + 18 > self.dim[1]:
                    break
                if isinstance(item, list):
                    if len(item) > 1:
                        points = [(x + 8, v_offset + 8 + (1.0 - y) * 30) for x, y in enumerate(item)]
                        pygame.draw.lines(display, (255, 136, 0), False, points, 2)
                    item = None
                    v_offset += 18
                elif isinstance(item, tuple):
                    if isinstance(item[1], bool):
                        rect = pygame.Rect((bar_h_offset, v_offset + 8), (6, 6))
                        pygame.draw.rect(display, (255, 255, 255), rect, 0 if item[1] else 1)
                    else:
                        rect_border = pygame.Rect((bar_h_offset, v_offset + 8), (bar_width, 6))
                        pygame.draw.rect(display, (255, 255, 255), rect_border, 1)
                        f = (item[1] - item[2]) / (item[3] - item[2])
                        if item[2] < 0.0:
                            rect = pygame.Rect((bar_h_offset + f * (bar_width - 6), v_offset + 8), (6, 6))
                        else:
                            rect = pygame.Rect((bar_h_offset, v_offset + 8), (f * bar_width, 6))
                        pygame.draw.rect(display, (255, 255, 255), rect)
                    item = item[0]
                if item:  # At this point has to be a str.
                    surface = self._font_mono.render(item, True, (255, 255, 255))
                    display.blit(surface, (8, v_offset))
                v_offset += 18
        self._notifications.render(display)

# ==============================================================================
# -- FadingText ----------------------------------------------------------------
# ==============================================================================

class FadingText(object):
    def __init__(self, font, dim, pos):
        self.font = font
        self.dim = dim
        self.pos = pos
        self.seconds_left = 0
        self.surface = pygame.Surface(self.dim)

    def set_text(self, text, color=(255, 255, 255), seconds=2.0):
        text_texture = self.font.render(text, True, color)
        self.surface = pygame.Surface(self.dim)
        self.seconds_left = seconds
        self.surface.fill((0, 0, 0, 0))
        self.surface.blit(text_texture, (10, 11))

    def tick(self, _, clock):
        delta_seconds = 1e-3 * clock.get_time()
        self.seconds_left = max(0.0, self.seconds_left - delta_seconds)
        self.surface.set_alpha(500.0 * self.seconds_left)

    def render(self, display):
        display.blit(self.surface, self.pos)


import numpy as np
import importlib

from statestream.utils.helper import is_scalar_shape
from statestream.backends.backends import import_backend

class Plasticity(object):
    """Wrapper class for plasticities.

    Parameters:
    -----------
    name : str
        The unique string identifier for the neuron-pool.
    net : dict
        Complete network dictionary containing all nps, sps, plasts, ifs.
    param : dict
        Dictionary of core parameters.
    mn : MetaNetwork
        Deprecated.
    nps : list of NeuronPool
        This list contains instances of all neuron-pools that are needed to
        compute the parameter updates of this plasticity. This may heavily
        vary between plasticity types (e.g. hebbian only needs the source
        and target np, where loss plasts may need many nps for local network
        rollout).
    sps : list of SynapsePool
        This list contains instances of all synapse-pools that are needed
        to compute the parameter updates of this plasticity (see np above).
    """
    def __init__(self, name, net, param, mn, nps, sps):
        # Import backend.
        self.B = import_backend(None, None, name)
        # Get / set global specifications.
        self.name = name
        self.net = net
        self.param = param
        self.mn = mn
        self.p = self.net["plasticities"][self.name]
        self.nps = nps
        self.sps = sps

        # Get start frame.
        self.startframe = self.p.get("startframe", 2)
        # Get plasticity type.
        self.type = self.p["type"]

        # Create list of all to be updated parameters.
        self.params = []
        self.params_id = []
        for p in self.p["parameter"]:
            self.params_id.append(p[0] + "." + p[1] + "." + p[2])
            if p[0] == "sp":
                try:
                    self.params.append(self.sps[p[1]].dat["parameter"][p[2]])
                except:
                    print("\nError: In plast: " + self.name + " collecting parameter: " \
                          + str(p[2]) + " for sp: " + str(p[1] \
                          + ". This may most certainly be caused by an inpropper depth " \
                          + "rollout for this plasticity."))
            if p[0] == "np":
                try:
                    self.params.append(self.nps[p[1]].dat["parameter"][p[2]])
                except:
                    print("\nError: In plast: " + self.name + " collecting parameter: " \
                          + str(p[2]) + " for np: " + str(p[1]))

        # Get shm layout for this plasticity.
        # ---------------------------------------------------------------------
        plast_shm_layout_fct \
            = getattr(importlib.import_module("statestream.meta.plasticities." \
                                              + self.type), "plast_shm_layout")
        self.dat_layout = plast_shm_layout_fct(self.name, self.net, self.param)
        self.dat = {}
        for t in ["parameter", "variables"]:
            settable = True
            self.dat[t] = {}
            for i, i_l in self.dat_layout[t].items():
                if i_l.type == "backend":
                    if is_scalar_shape(i_l.shape):
                        self.dat[t][i] = self.B.scalar(0.0,
                                                       dtype=np.float32,
                                                       name=name + "." + i,
                                                       settable=settable)
                    else:
                        self.dat[t][i] = self.B.variable(np.zeros(i_l.shape, dtype=i_l.dtype),
                                                         borrow=True,
                                                         name=name + "." + i,
                                                         settable=settable)
                elif i_l.type == "np":
                    if is_scalar_shape(i_l.shape):
                        self.dat[t][i] = np.array([1,], dtype=i_l.dtype)
                    else:
                        self.dat[t][i] = np.array(i_l.shape, dtype=i_l.dtype)

        # First initialization of plasticity variables (mostly from optimizer).
        for i, i_l in self.dat_layout["variables"].items():
            if i_l.type == "backend":
                value = None
                if is_scalar_shape(i_l.shape):
                    value = 0.0
                else:
                    value = np.zeros(i_l.shape, dtype=i_l.dtype)
                self.B.set_value(self.dat["variables"][i], value)

        # Instantiate split parameter.
        self.split = self.B.variable(np.zeros([self.net['agents'],], dtype=np.float32),
                                     borrow=True,
                                     name=name + '.' + 'split')

        # Define function for sp/np parameter update (e.g. optimization).
        self.update_parameter = lambda: None


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

class Migration(migrations.Migration):

    dependencies = [
        ('tandem', '0006_tandemfile_project_name'),
    ]

    operations = [
        migrations.RemoveField(
            model_name='tandemfile',
            name='project_name',
        ),
        migrations.DeleteModel(
            name='Tandemfile',
        ),
    ]


import ply.lex as lex
import sys
import re

file = open("read.py")

# Diccionario de palabras reservadas
reservadas = {
    'if': 'IF',
    'else': 'ELSE',
    'while': 'WHILE',
    'for': 'FOR',
    'break': 'BREAK',
    'continue': 'CONTINUE',
    'True': 'TRUE',
    'False': 'FALSE',
    'and': 'AND',
    'or': 'OR',
    'not': 'NOT',
}

# Lista de tokens
tokens = [
    'ID',
    'NUMBER',
    'PLUS',
    'MINUS',
    'TIMES',
    'DIVIDE',
    'EQUALS',
    'LPAREN',
    'RPAREN',
    'LBRACE',
    'RBRACE',
    'COMMA',
    'SEMI',
    'GREATER',
    'LESS',
    'GREATER_EQUAL',
    'LESS_EQUAL',
    'DOUBLE_EQUAL',
    'NOT_EQUAL',
] + list(reservadas.values())

# Expresiones regulares para tokens simples
t_PLUS = r'\+'
t_MINUS = r'-'
t_TIMES = r'\*'
t_DIVIDE = r'/'
t_EQUALS = r'='
t_LPAREN = r'\('
t_RPAREN = r'\)'
t_LBRACE = r'{'
t_RBRACE = r'}'
t_COMMA = r','
t_SEMI = r';'
t_GREATER = r'>'
t_LESS = r'<'
t_GREATER_EQUAL = r'>='
t_LESS_EQUAL = r'<='
t_DOUBLE_EQUAL = r'=='
t_NOT_EQUAL = r'!='

# Expresiones regulares con acciones asociadas
def t_ID(t):
    r'[a-zA-Z_][a-zA-Z0-9_]*'
    t.type = reservadas.get(t.value, 'ID')
    return t

def t_NUMBER(t):
    r'\d+'
    t.value = int(t.value)
    return t

# Manejo de espacios en blanco y comentarios
def t_whitespace(t):
    r'\s+'
    pass

def t_comment(t):
    r'\#.*'
    pass

# Manejo de errores
def t_error(t):
    print(f"Carácter ilegal '{t.value[0]}' en línea {t.lineno}, posición {t.lexpos}")
    t.lexer.skip(1)

# Construcción del lexer
lexer = lex.lex()
contents = file.read()
print("Analizador Léxico")
print("----------------------------------")

count=0
program = contents.split("\n")
print("Archivo leido\n",contents)
print("----------------------------------")
print("Division en tokens")
for line in program:
    count = count + 1
    tokens=line.split(' ')
    print("Linea ",count, " Tokens: ",tokens)

# Leer archivo de texto
#filename = 'read.py'
#with open(filename, "r") as file:
    #contents = file.read()
    #tokens=line.split(' ')
    #print("Los tokens son:" , tokens)

# Análisis léxico
print("\n CLASIFICACION DE TOKENS \n")
lexer.input(contents)
for tok in lexer:
    print(f"Linea {tok.lineno:3} Posicion {tok.lexpos:3} Tipo {tok.type:16} Valor {tok.value:10}")


import tensorflow as tf
from numpy import *
import matplotlib.pyplot as plt
def loadDataSet():
    dataMat = []
    labelMat = []
    fr = open('testSet.txt')
    for line in fr.readlines() :
        lineArr = line.strip().split()
        dataMat.append([1.0,float(lineArr[0]),float(lineArr[1])])
        labelMat.append(int(lineArr[2]))

    print(dataMat)
    return dataMat , labelMat

def sigmoid(inX):
    return 1.0/(1+exp(-inX))

def gradAscent(dataMatIn, classLabels):
    dataMatrix = mat(dataMatIn)
    labelMat = mat(classLabels).transpose()
    m,n = shape(dataMatrix)
    print(m,n)
    alpha = 0.001
    maxCycles = 1000
    weights = ones((n,1))
    for k in range(maxCycles) :
        h = sigmoid(dataMatrix*weights)
        error = (labelMat - h)
        weights = weights + alpha * dataMatrix.transpose()*error
    return weights

def plotBestFit(weights):
    dataMat, labelMat = loadDataSet()
    dataArr = array(dataMat)
    n = shape(dataArr)[0]
    xcord1 = []
    xcord2 = []
    ycord1 = []
    ycord2 = []
    for i in range(n) :
        if int(labelMat[i]) == 1 :
            xcord1.append(dataArr[i,1])
            ycord1.append(dataArr[i,2])
        else :
            xcord2.append(dataArr[i,1])
            ycord2.append(dataArr[i,2])
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(xcord1,ycord1,s=30,c='red',marker='s')
    ax.scatter(xcord2,ycord2,s=30,c='green')
    x = arange(-3.0,3.0,0.1)
    y = array((-weights[0]-weights[1]*x)/weights[2])
    y = reshape(y,60)
    ax.plot(x,y)
    plt.xlabel('X1')
    plt.ylabel('X2')
    plt.show()

In , label = loadDataSet()
weight = gradAscent(In, label)
plotBestFit(weight)


import numpy as np
from traitlets import Enum, Integer, default, Float, Bool, Unicode, List

from mrHARDI.base.application import (mrHARDIConfigurable,
                                           convert_enum,
                                           ChoiceList)

from mrHARDI.base.dwi import Direction, AcquisitionType
from mrHARDI.compute.b0 import B0PostProcess, B0Reference

_b0_aliases = dict(
    mean="B0UtilsConfiguration.mean_strategy",
    ref="B0UtilsConfiguration.reference_strategy",
    type="B0UtilsConfiguration.dtype",
    strides="B0UtilsConfiguration.strides",
    ceil="B0UtilsConfiguration.ceil_value"
)

_b0_flags = dict(
    strict=(
        {'B0UtilsConfiguration': {'strict': True}},
        "Test b0 b-values using \"less\" comparator "
        "instead of \"less or equal\""
    ),
)

class B0UtilsConfiguration(mrHARDIConfigurable):
    def _validate(self):
        pass

    def serialize(self, *args, **kwargs):
        pass

    @default("app_aliases")
    def _app_aliases_default(self):
        return _b0_aliases

    @default("app_flags")
    def _app_flags_default(self):
        return _b0_flags

    current_util = Enum(
        ["extract", "squash", "normalize", None], None
    ).tag(config=True, required=True)

    mean_strategy = convert_enum(
        B0PostProcess, B0PostProcess.batch, allow_none=True,
        description="Strategy to use when averaging the b0 volume(s). "
                    "Either averages the whole set of b0s (whole), does "
                    "the averages by clump of continuous b0 (batch) or "
                    "takes the first on of each clump (none)."
    ).tag(config=True)

    reference_strategy = convert_enum(
        B0Reference, B0Reference.linear,
        description="ONLY USED WHEN DOING NORMALIZATION. Defines which "
                    "strategy will be used to select b0 volumes for "
                    "averaging. Can either do a linear interpolation between "
                    "b0 volumes appearing before and after each group of "
                    "diffusion volumes (linear), take the one appearing "
                    "before (first) or after (last)."
    ).tag(config=True)

    ceil_value = Float(
        0.9, help="Higher bound determining a valid b-value for a b0 volume"
    ).tag(config=True)

    strict = Bool(
        False, help="If True, test b0 b-values with "
                    "\"<\" comparator instead of \"<=\""
    ).tag(config=True)

    dtype = Enum(
        [
            np.dtype(t).name
            for t in [np.int16, np.int32, np.float32, np.float64]
        ], allow_none=True
    ).tag(config=True)

    strides = Integer(None, allow_none=True).tag(config=True)

    def get_mean_strategy_enum(self):
        return B0PostProcess[self.mean_strategy]

    def get_ref_strategy_enum(self):
        return B0Reference[self.reference_strategy]

_meta_aliases = dict(
    dir="DwiMetadataUtilsConfiguration.direction",
    mb="DwiMetadataUtilsConfiguration.multiband_factor",
    sd="DwiMetadataUtilsConfiguration.slice_direction",
    si="DwiMetadataUtilsConfiguration.slice_indexes",
    gsl="DwiMetadataUtilsConfiguration.gslider_factor",
    acq="DwiMetadataUtilsConfiguration.acquisition",
    readout="DwiMetadataUtilsConfiguration.readout",
    coils="DwiMetadataUtilsConfiguration.number_of_coils"
)

_meta_flags = dict(
    interleaved=(
        {"DwiMetadataUtilsConfiguration": {'interleaved': True}},
        "Set dataset acquisition as interleaved order (0, 2, 4, 1, 3 ...)"
    ),
    mbc=(
        {"DwiMetadataUtilsConfiguration": {'multiband_corrected': True}},
        "Set dataset as already corrected for multiband artifacts"
    ),
    tv=(
        {"DwiMetadataUtilsConfiguration": {'tensor_valued': True}},
        "Set dataset as tensor-valued acquisition"
    )
)

class DwiMetadataUtilsConfiguration(mrHARDIConfigurable):
    @default("app_aliases")
    def _app_aliases_default(self):
        return _meta_aliases

    @default("app_flags")
    def _app_flags_default(self):
        return _meta_flags

    direction = ChoiceList(
        [d.name for d in Direction], Unicode(),
        help="List of phase encoding directions of the input datasets. "
             "There can be N directions, with N either :\n"
             "   - 1 : the same direction will apply to all datasets\n"
             "   - N : one direction per dataset, applied to all slices"
    ).tag(config=True, required=True)

    slice_direction = ChoiceList(
        [d.name for d in Direction], Unicode(),
        help="List of acquisition directions of the input datasets. There can "
             "be N directions, with N either :\n"
             "   - 1 : the same direction will apply to all datasets\n"
             "   - N : one direction per dataset, applied to all slices"
    ).tag(config=True)

    readout = Float(help="Acquisition readout time (in ms)").tag(
        config=True, required=True
    )

    gslider_factor = Integer(None, allow_none=True).tag(config=True)

    multiband_factor = Integer(1, allow_none=True).tag(config=True)

    interleaved = Bool(
        None, allow_none=True,
        help="If True, will output interleaved "
             "slices in the multiband index definition"
    ).tag(config=True)

    slice_indexes = List(
        List(Integer()), None, allow_none=True
    ).tag(config=True)

    multiband_corrected = Bool(
        False, allow_none=True,
        help="Specifies if the dataset has been corrected "
             "for iter-slice and slice-to-vol motion"
    ).tag(config=True)

    tensor_valued = Bool(
        None, allow_none=True, help="Dataset acquisition is tensor-valued"
    ).tag(config=True)

    acquisition = convert_enum(
        AcquisitionType, None, allow_none=True
    ).tag(config=True)

    number_of_coils = Integer(
        0, help="Number of coils used for the image acquisition"
    )

    def _validate(self):
        pass

    def serialize(self, *args, **kwargs):
        pass


# 数据读取以及预处理方法的脚本
import math
import os
import random
import shutil
from pathlib import Path

import cv2
import numpy as np
import torch
from PIL import Image, ExifTags
from torch.utils.data import Dataset
from tqdm import tqdm

from build_utils.utils import xyxy2xywh, xywh2xyxy

help_url = 'https://github.com/ultralytics/yolov3/wiki/Train-Custom-Data'
img_formats = ['.bmp', '.jpg', '.jpeg', '.png', '.tif', '.dng']

# cv2.setNumThreads(0)  # 当dataloader中num_workers > 0时, 需禁用OpenCV多线程，防止dataloader使用多线程，出现套娃死锁

# get orientation in exif tag
# 找到图像exif信息中对应旋转信息的key值
for orientation in ExifTags.TAGS.keys():
    if ExifTags.TAGS[orientation] == "Orientation":
        break

def exif_size(img):
    """
    获取图像的原始img size
    通过exif的orientation信息判断图像是否有旋转，如果有旋转则返回旋转前的size
    :param img: PIL图片
    :return: 原始图像的size
    """
    # Returns exif-corrected PIL size
    s = img.size  # (width, height)  用Image.open()对象的size类获取（width，height）
    try:
        rotation = dict(img._getexif().items())[orientation]  # 获取图片的exif信息中orientation
        if rotation == 6:  # rotation 270  顺时针翻转90度
            s = (s[1], s[0])
        elif rotation == 8:  # ratation 90  逆时针翻转90度
            s = (s[1], s[0])
    except:
        # 如果图像的exif信息中没有旋转信息，则跳过
        pass

    return s

class LoadImagesAndLabels(Dataset):  # for training/testing  自定义数据集部分
    def __init__(self,
                 path,   # 指向data/my_train_data.txt路径或data/my_val_data.txt路径
                 # 这里设置的是预处理后输出的图片尺寸
                 # 当为训练集时，设置的是训练过程中(开启多尺度)的最大尺寸
                 # 当为验证集时，设置的是最终使用的网络大小
                 img_size=416,
                 batch_size=16,
                 augment=False,  # 训练集设置为True(augment_hsv)，验证集设置为False，是关于图像颜色，亮度，对比度等等的增强
                 hyp=None,  # 超参数字典，其中包含图像增强会使用到的超参数
                 rect=False,  # 是否使用rectangular training 训练集设为False 验证集设为True
                 cache_images=False,  # 是否缓存图片到内存中
                 single_cls=False, pad=0.0, rank=-1):  # rank=-1默认使用但GPU训练，若使用多GPU会开启多个进程0,1,2，。。。

        try:
            path = str(Path(path))  # Path()创建路径对象,注意Path对象要str原因是，python3.5以下，不能将Path对象作为open的参数
            # parent = str(Path(path).parent) + os.sep
            if os.path.isfile(path):  # file
                # 读取对应my_train/val_data.txt文件，读取每一行的图片路径信息
                with open(path, "r") as f:
                    f = f.read().splitlines()  # .read()返回整个文件字符串.splitlines()返回包含各行作为元素的列表
            else:
                raise Exception("%s does not exist" % path)

            # 检查每张图片后缀格式是否在支持的列表中，保存支持的图像路径
            # img_formats = ['.bmp', '.jpg', '.jpeg', '.png', '.tif', '.dng']
            # img_files = ['./my_yolo_dataset/train/images\2008_000008.jpg', ...]
            self.img_files = [x for x in f if os.path.splitext(x)[-1].lower() in img_formats]  # os.path.splitext()将文件名和扩展名分开，这里返回.jpg
            self.img_files.sort()  # 防止不同系统排序不同，导致shape文件出现差异
        except Exception as e:
            raise FileNotFoundError("Error loading data from {}. {}".format(path, e))

        # 如果图片列表中没有图片，则报错
        n = len(self.img_files)
        assert n > 0, "No images found in %s. See %s" % (path, help_url)

        # batch index
        # 将数据划分到一个个batch中,np.floor为向下取整,返回小数类型， 使用//下取整也可以，但统一为np操作更好
        bi = np.floor(np.arange(n) / batch_size).astype(int)  # 如[0,1,2,3,4,5,6,7,8,9],batch_size=4,bi=[0,0,0,0,1,1,1,1,2,2]
        # 记录数据集划分后的总batch数
        nb = bi[-1] + 1  # number of batches

        self.n = n  # number of images 图像总数目
        self.batch = bi  # batch index of image 记录哪些图片属于哪个batch
        self.img_size = img_size  # 这里设置的是预处理后输出的图片尺寸
        self.augment = augment  # 是否启用augment_hsv
        self.hyp = hyp  # 超参数字典，其中包含图像增强会使用到的超参数
        self.rect = rect  # 是否使用rectangular training
        # 注意: 开启rect后，mosaic就默认关闭
        self.mosaic = self.augment and not self.rect  # load 4 images at a time into a mosaic (only during training)

        # Define labels
        # 遍历设置图像对应的label路径
        # (./my_yolo_dataset/train/images/2009_004012.jpg) -> (./my_yolo_dataset/train/labels/2009_004012.txt)
        self.label_files = [x.replace("images", "labels").replace(os.path.splitext(x)[-1], ".txt")
                            for x in self.img_files]

        # Read image shapes (wh)
        # 查看data文件下是否缓存有对应数据集的.shapes文件，里面存储了每张图像的width, height
        sp = path.replace(".txt", ".shapes")  # shapefile path
        try:
            with open(sp, "r") as f:  # read existing shapefile,注意第一次读取时还没有.shapes文件，此时跳转到except
                s = [x.split() for x in f.read().splitlines()]  # s = [[500 442], [500 327],...]
                # 判断现有的shape文件中的行数(图像个数)是否与当前数据集中图像个数相等
                # 如果不相等则认为是不同的数据集，故重新生成shape文件
                assert len(s) == n, "shapefile out of aync"
        except Exception as e:
            # print("read {} failed [{}], rebuild {}.".format(sp, e, sp))
            # tqdm库会显示处理的进度
            # 读取每张图片的size信息
            if rank in [-1, 0]:  # 使用单GPU或主进程读取数据时，可视化进度信息
                image_files = tqdm(self.img_files, desc="Reading image shapes")
            else:
                image_files = self.img_files
            s = [exif_size(Image.open(f)) for f in image_files]  # Image.open打开的图片是PIL类型(H,W,C)，通道默认RGB。这一步会可视化
            # 将所有图片的shape信息保存在.shape文件中
            np.savetxt(sp, s, fmt="%g")  # overwrite existing (if any) np.savetxt()可写入一维或二维数组

        # 记录每张图像的原始尺寸
        self.shapes = np.array(s, dtype=np.float64)  # self.shapes为一ndarray数组

        # Rectangular Training https://github.com/ultralytics/yolov3/issues/232
        # 如果为ture，训练网络时，会使用类似原图像比例的矩形(让最长边为img_size)，而不是img_size x img_size
        # 注意: 开启rect后，mosaic就默认关闭
        if self.rect:
            # Sort by aspect ratio
            s = self.shapes  # wh
            # 计算每个图片的高/宽比
            ar = s[:, 1] / s[:, 0]  # aspect ratio H/W
            # argsort函数返回的是数组值从小到大的索引值
            # 按照高宽比例进行排序，这样后面划分的每个batch中的图像就拥有类似的高宽比
            irect = ar.argsort()
            # 根据排序后的顺序重新设置图像顺序、标签顺序以及shape顺序
            self.img_files = [self.img_files[i] for i in irect]
            self.label_files = [self.label_files[i] for i in irect]
            self.shapes = s[irect]  # wh
            ar = ar[irect]  # 将高宽比ar从小到大排序

            # set training image shapes
            # 计算每个batch采用的统一尺度
            shapes = [[1, 1]] * nb  # nb: number of batches
            for i in range(nb):  # i表示当前为第i个batch，此循环模块用于调整并统一第i个batch中图片的缩放比例
                ari = ar[bi == i]  # bi: batch index 给ar按bi划分batch，如bi=[0,0,0,1,1,1],第一次的ari等于ar中前三个元素
                # 获取第i个batch中，最小和最大高宽比
                mini, maxi = ari.min(), ari.max()

                # 如果高/宽小于1(w > h)，将w设为img_size
                if maxi < 1:  # 此时w为长边，将w缩放至img_size大小，h按原先ar中的高宽比等比例缩放
                    shapes[i] = [maxi, 1]
                # 如果高/宽大于1(w < h)，将h设置为img_size
                elif mini > 1:  # 此时h为长边，将h缩放至img_size大小，w按原先ar中的高宽比等比例缩放
                    shapes[i] = [1, 1 / mini]
            # 计算每个batch输入网络的shape值(向上取整设置为32的整数倍) np.ceil(1.5)->2，注意此时已从[w,h]换成[h,w]
            self.batch_shapes = np.ceil(np.array(shapes) * img_size / 32. + pad).astype(int) * 32

        # cache labels  加载图片标签
        self.imgs = [None] * n  # n为图像总数，这里self.imgs将用来设置加载图片至cache
        # label: [class, x, y, w, h] 其中的xywh都为相对值
        self.labels = [np.zeros((0, 5), dtype=np.float32)] * n
        extract_bounding_boxes, labels_loaded = False, False
        nm, nf, ne, nd = 0, 0, 0, 0  # number mission, found, empty, duplicate
        # 这里分别命名是为了防止出现rect为False/True时混用导致计算的mAP错误
        # 当rect为True时会对self.images和self.labels进行从新排序
        if rect is True:  # Path(path).parent获取path的上一级目录的路径
            np_labels_path = str(Path(self.label_files[0]).parent) + ".rect.npy"  # saved labels in *.npy file
        else:
            np_labels_path = str(Path(self.label_files[0]).parent) + ".norect.npy"
        # 判断是否存在缓存文件，存在则将缓存文件赋给self.labels
        if os.path.isfile(np_labels_path):  # isfile不一定需要绝对路径
            x = np.load(np_labels_path, allow_pickle=True)  # np.load加载np.save保存的文件(.npy)，allow_pickle=True允许加载pickle序列化后的对象
            if len(x) == n:
                # 如果载入的缓存标签个数(实际是图像个数)与当前计算的图像数目相同则认为是同一数据集，直接读缓存
                self.labels = x  # self.labels为一列表，每个元素是(a, 5)大小的ndarray数组，其中a表示图片中标注的目标数
                labels_loaded = True

        # 处理进度条只在第一个进程中显示
        if rank in [-1, 0]:
            pbar = tqdm(self.label_files)  # label_files=['./my_yolo_dataset/train/labels/2009_004012.txt',...],
                                           # 若rect=True,label_files会重新按高宽比ar重新排序
        else:
            pbar = self.label_files

        # 遍历载入标签文件
        for i, file in enumerate(pbar):
            if labels_loaded is True:  # 当有缓存标签数据，则labels_loaded设为True，见178-183
                # 如果存在缓存直接从缓存读取
                l = self.labels[i]  # l为第i幅图像的标签信息，大小为(a,5)，为ndarray类型
            else:
                # 从文件读取标签信息
                try:
                    with open(file, "r") as f:
                        # 读取每一行label，并按空格划分数据
                        l = np.array([x.split() for x in f.read().splitlines()], dtype=np.float32)
                except Exception as e:
                    print("An error occurred while loading the file {}: {}".format(file, e))
                    nm += 1  # file missing
                    continue

            # 如果标注信息不为空的话
            if l.shape[0]:
                # 标签信息每行必须是五个值[class, x, y, w, h]
                assert l.shape[1] == 5, "> 5 label columns: %s" % file
                assert (l >= 0).all(), "negative labels: %s" % file  # 判断(a, 5)中每个元素是否大于等于0
                assert (l[:, 1:] <= 1).all(), "non-normalized or out of bounds coordinate labels: %s" % file  # 判断坐标是否归一化

                # 检查每一行，看是否有重复信息
                if np.unique(l, axis=0).shape[0] < l.shape[0]:  # duplicate rows
                    nd += 1
                if single_cls:
                    l[:, 0] = 0  # force dataset into single-class mode

                self.labels[i] = l  # 第一次没有.npy缓存文件时，self.labels还没赋值标签信息，所以在该步进行
                nf += 1  # file found

                # Extract object detection boxes for a second stage classifier，该模块用于二阶段目标检测模型
                if extract_bounding_boxes:
                    p = Path(self.img_files[i])
                    img = cv2.imread(str(p))
                    h, w = img.shape[:2]
                    for j, x in enumerate(l):
                        f = "%s%sclassifier%s%g_%g_%s" % (p.parent.parent, os.sep, os.sep, x[0], j, p.name)
                        if not os.path.exists(Path(f).parent):
                            os.makedirs(Path(f).parent)  # make new output folder

                        # 将相对坐标转为绝对坐标
                        # b: x, y, w, h
                        b = x[1:] * [w, h, w, h]  # box
                        # 将宽和高设置为宽和高中的最大值
                        b[2:] = b[2:].max()  # rectangle to square
                        # 放大裁剪目标的宽高
                        b[2:] = b[2:] * 1.3 + 30  # pad
                        # 将坐标格式从 x,y,w,h -> xmin,ymin,xmax,ymax
                        b = xywh2xyxy(b.reshape(-1, 4)).revel().astype(np.int)

                        # 裁剪bbox坐标到图片内
                        b[[0, 2]] = np.clip[b[[0, 2]], 0, w]
                        b[[1, 3]] = np.clip[b[[1, 3]], 0, h]
                        assert cv2.imwrite(f, img[b[1]:b[3], b[0]:b[2]]), "Failure extracting classifier boxes"
            else:
                ne += 1  # file empty

            # 处理进度条只在第一个进程中显示
            if rank in [-1, 0]:
                # 更新进度条描述信息
                pbar.desc = "Caching labels (%g found, %g missing, %g empty, %g duplicate, for %g images)" % (
                    nf, nm, ne, nd, n)
        assert nf > 0, "No labels found in %s." % os.path.dirname(self.label_files[0]) + os.sep

        # 如果标签信息没有被保存成numpy的格式，且训练样本数大于1000则将标签信息保存成numpy的格式
        if not labels_loaded and n > 1000:
            print("Saving labels to %s for faster future loading" % np_labels_path)
            np.save(np_labels_path, self.labels)  # save for next time

        # Cache images into memory for faster training (Warning: large datasets may exceed system RAM)
        if cache_images:  # if training  把图片加载入缓存，即加载图片放到self.imgs中，后续__getitem__()中的load_image实现更快
            gb = 0  # Gigabytes of cached images 用于记录缓存图像占用RAM大小
            if rank in [-1, 0]:
                pbar = tqdm(range(len(self.img_files)), desc="Caching images")
            else:
                pbar = range(len(self.img_files))

            self.img_hw0, self.img_hw = [None] * n, [None] * n
            for i in pbar:  # max 10k images
                self.imgs[i], self.img_hw0[i], self.img_hw[i] = load_image(self, i)  # img, hw_original, hw_resized
                gb += self.imgs[i].nbytes  # 用于记录缓存图像占用RAM大小
                if rank in [-1, 0]:
                    pbar.desc = "Caching images (%.1fGB)" % (gb / 1E9)
        # 检测图像损坏
        # Detect corrupted images https://medium.com/joelthchao/programmatically-detect-corrupted-image-8c1b2006c3d3
        detect_corrupted_images = False
        if detect_corrupted_images:
            from skimage import io  # conda install -c conda-forge scikit-image
            for file in tqdm(self.img_files, desc="Detecting corrupted images"):
                try:
                    _ = io.imread(file)
                except Exception as e:
                    print("Corrupted image detected: {}, {}".format(file, e))

    def __len__(self):
        return len(self.img_files)

    def __getitem__(self, index):  # 实现一系列预处理方法，并根据index返回image和label
        hyp = self.hyp
        if self.mosaic:  # 训练时开启
            # load mosaic
            img, labels = load_mosaic(self, index)
            shapes = None
        else:
            # load image
            img, (h0, w0), (h, w) = load_image(self, index)  # 此方法加载的图片最长边已缩放至img_size，另一边等比例缩放

            # letterbox
            shape = self.batch_shapes[self.batch[index]] if self.rect else self.img_size  # final letterboxed shape
            img, ratio, pad = letterbox(img, shape, auto=False, scale_up=self.augment)  # 将图片裁剪到指定大小
            shapes = (h0, w0), ((h / h0, w / w0), pad)  # for COCO mAP rescaling

            # load labels
            labels = []
            x = self.labels[index]  # self.labels中每个元素的大小为(a,5)，为ndarray类型
            if x.size > 0:
                # Normalized xywh to pixel xyxy format 因为数据增强需要用到左上和右下的坐标
                labels = x.copy()  # label: class, x, y, w, h
                labels[:, 1] = ratio[0] * w * (x[:, 1] - x[:, 3] / 2) + pad[0]  # pad width
                labels[:, 2] = ratio[1] * h * (x[:, 2] - x[:, 4] / 2) + pad[1]  # pad height
                labels[:, 3] = ratio[0] * w * (x[:, 1] + x[:, 3] / 2) + pad[0]
                labels[:, 4] = ratio[1] * h * (x[:, 2] + x[:, 4] / 2) + pad[1]

        if self.augment:  # 训练集开启，验证集关闭，对图像进行HSV变换
            # Augment imagespace
            if not self.mosaic:
                img, labels = random_affine(img, labels,
                                            degrees=hyp["degrees"],
                                            translate=hyp["translate"],
                                            scale=hyp["scale"],
                                            shear=hyp["shear"])

            # Augment colorspace
            augment_hsv(img, h_gain=hyp["hsv_h"], s_gain=hyp["hsv_s"], v_gain=hyp["hsv_v"])

        nL = len(labels)  # number of labels
        if nL:
            # convert xyxy to xywh  ，数据增强完毕后，将绝对坐标xyxy转换回xywh
            labels[:, 1:5] = xyxy2xywh(labels[:, 1:5])

            # Normalize coordinates 0-1  将绝对坐标转化成相对坐标
            labels[:, [2, 4]] /= img.shape[0]  # height
            labels[:, [1, 3]] /= img.shape[1]  # width

        if self.augment:
            # random left-right flip
            lr_flip = True  # 随机水平翻转
            if lr_flip and random.random() < 0.5:  # random.random()随机生成一个0-1之间的实数
                img = np.fliplr(img)  # 由cv2.操作得到的图像为ndarray格式
                if nL:
                    labels[:, 1] = 1 - labels[:, 1]  # 1 - x_center

            # random up-down flip
            ud_flip = False
            if ud_flip and random.random() < 0.5:
                img = np.flipud(img)
                if nL:
                    labels[:, 2] = 1 - labels[:, 2]  # 1 - y_center

        labels_out = torch.zeros((nL, 6))  # nL: number of labels
        if nL:
            labels_out[:, 1:] = torch.from_numpy(labels)  # 注意labels_out[:, 0]索引为0的元素此处默认为0,增加一维是为了后面区分标签是属于哪张图片的

        # Convert BGR to RGB, and HWC to CHW(3x512x512)
        img = img[:, :, ::-1].transpose(2, 0, 1)  # 转换到pytorch部署网络需要的输入格式
        img = np.ascontiguousarray(img)  # 将img内存连续化

        return torch.from_numpy(img), labels_out, self.img_files[index], shapes, index

    def coco_index(self, index):
        """该方法是专门为cocotools统计标签信息准备，不对图像和标签作任何处理"""
        o_shapes = self.shapes[index][::-1]  # wh to hw

        # load labels
        x = self.labels[index]
        labels = x.copy()  # label: class, x, y, w, h
        return torch.from_numpy(labels), o_shapes

    @staticmethod
    def collate_fn(batch):  # 定义DataLoader的读取方法
        img, label, path, shapes, index = zip(*batch)  # transposed
        for i, l in enumerate(label):  # label={tuple:4},每个元素是一(nt,6)的tensor，nt为某张图片的目标数
            l[:, 0] = i  # add target image index for build_targets() i表示每个batch中第i张图片
        return torch.stack(img, 0), torch.cat(label, 0), path, shapes, index  # torch.stack(img, 0)将tuple中batchsize个img元素变成tensor(B,C,H,W)

def load_image(self, index):  # 加载后的图片的最长边已缩放至img_size，另一边等比例缩放
    # loads 1 image from dataset, returns img, original hw, resized hw
    img = self.imgs[index]  # self.imgs = [None] * n
    if img is None:  # not cached
        path = self.img_files[index]
        img = cv2.imread(path)  # BGR
        assert img is not None, "Image Not Found " + path
        h0, w0 = img.shape[:2]  # orig hw
        # img_size 设置的是预处理后输出的图片尺寸
        r = self.img_size / max(h0, w0)  # resize image to img_size
        if r != 1:  # if sizes are not equal
            interp = cv2.INTER_AREA if r < 1 and not self.augment else cv2.INTER_LINEAR  # 一种插值方法，INTER_AREA（约等于平均池化）效果最好，INTER_LINEAR最快
            img = cv2.resize(img, (int(w0 * r), int(h0 * r)), interpolation=interp)
        return img, (h0, w0), img.shape[:2]  # img, hw_original, hw_resized
    else:
        return self.imgs[index], self.img_hw0[index], self.img_hw[index]  # img, hw_original, hw_resized

def load_mosaic(self, index):
    """
    将四张图片拼接在一张马赛克图像中
    :param self:
    :param index: 需要获取的图像索引
    :return:
    """
    # loads images in a mosaic

    labels4 = []  # 拼接图像的label信息
    s = self.img_size
    # 随机初始化拼接图像的中心点坐标,random.uniform(x，y)方法随机生成一个实数，它在[x,y]范围内
    xc, yc = [int(random.uniform(s * 0.5, s * 1.5)) for _ in range(2)]  # mosaic center x, y
    # 从dataset中随机寻找三张图像进行拼接，random.randint函数返回参数1和参数2之间的任意整数，闭区间
    indices = [index] + [random.randint(0, len(self.labels) - 1) for _ in range(3)]  # 3 additional image indices
    # 遍历四张图像进行拼接
    for i, index in enumerate(indices):
        # load image
        img, _, (h, w) = load_image(self, index)  # 经load_image处理后的图像最长边缩放至img_size，另一边等比例缩放

        # place img in img4
        if i == 0:  # top left
            # 创建马赛克图像
            img4 = np.full((s * 2, s * 2, img.shape[2]), 114, dtype=np.uint8)  # base image with 4 tiles
            # 计算马赛克图像中的坐标信息(将图像填充到马赛克图像中)
            x1a, y1a, x2a, y2a = max(xc - w, 0), max(yc - h, 0), xc, yc  # xmin, ymin, xmax, ymax (large image)
            # 计算截取的图像区域信息(以xc,yc为第一张图像的右下角坐标填充到马赛克图像中，丢弃越界的区域)，裁剪区域在原图像上的坐标
            x1b, y1b, x2b, y2b = w - (x2a - x1a), h - (y2a - y1a), w, h  # xmin, ymin, xmax, ymax (small image)
        elif i == 1:  # top right
            # 计算马赛克图像中的坐标信息(将图像填充到马赛克图像中)
            x1a, y1a, x2a, y2a = xc, max(yc - h, 0), min(xc + w, s * 2), yc
            # 计算截取的图像区域信息(以xc,yc为第二张图像的左下角坐标填充到马赛克图像中，丢弃越界的区域)
            x1b, y1b, x2b, y2b = 0, h - (y2a - y1a), min(w, x2a - x1a), h
        elif i == 2:  # bottom left
            # 计算马赛克图像中的坐标信息(将图像填充到马赛克图像中)
            x1a, y1a, x2a, y2a = max(xc - w, 0), yc, xc, min(s * 2, yc + h)
            # 计算截取的图像区域信息(以xc,yc为第三张图像的右上角坐标填充到马赛克图像中，丢弃越界的区域)
            x1b, y1b, x2b, y2b = w - (x2a - x1a), 0, max(xc, w), min(y2a - y1a, h)
        elif i == 3:  # bottom right
            # 计算马赛克图像中的坐标信息(将图像填充到马赛克图像中)
            x1a, y1a, x2a, y2a = xc, yc, min(xc + w, s * 2), min(s * 2, yc + h)
            # 计算截取的图像区域信息(以xc,yc为第四张图像的左上角坐标填充到马赛克图像中，丢弃越界的区域)
            x1b, y1b, x2b, y2b = 0, 0, min(w, x2a - x1a), min(y2a - y1a, h)

        # 将截取的图像区域填充到马赛克图像的相应位置
        img4[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b]  # img4[ymin:ymax, xmin:xmax]

        # 计算pad(图像边界与马赛克边界的距离，越界的情况为负值)
        padw = x1a - x1b
        padh = y1a - y1b

        # Labels 获取对应拼接图像的labels信息
        # [class_index, x_center, y_center, w, h]
        x = self.labels[index]  # self.labels为一列表，每个元素是(a, 5)大小的ndarray数组，其中a表示图片中标注的目标数
        labels = x.copy()  # 深拷贝，防止修改原数据
        if x.size > 0:  # Normalized xywh to pixel xyxy format
            # 计算标注数据在马赛克图像中的坐标(绝对坐标)
            labels[:, 1] = w * (x[:, 1] - x[:, 3] / 2) + padw   # xmin
            labels[:, 2] = h * (x[:, 2] - x[:, 4] / 2) + padh   # ymin
            labels[:, 3] = w * (x[:, 1] + x[:, 3] / 2) + padw   # xmax
            labels[:, 4] = h * (x[:, 2] + x[:, 4] / 2) + padh   # ymax
        labels4.append(labels)  # labels4为拼接后的标签信息

    # cv2.imshow('mosaic_img', img4)  # debug显示mosaic
    # key = cv2.waitKey(0)

    # Concat/clip labels
    if len(labels4):
        labels4 = np.concatenate(labels4, 0)  # np.concatenate()是用来对数列或矩阵进行合并的, axis=0为按行进行合并
        # 设置上下限防止越界，将labels4[:, 1:]中的数限定到0-2s，即将标签坐标裁剪，与之前的拼接裁剪操作统一
        np.clip(labels4[:, 1:], 0, 2 * s, out=labels4[:, 1:])  # use with random_affine

    # Augment
    # 随机旋转，缩放，平移以及错切
    img4, labels4 = random_affine(img4, labels4,
                                  degrees=self.hyp['degrees'],
                                  translate=self.hyp['translate'],
                                  scale=self.hyp['scale'],
                                  shear=self.hyp['shear'],
                                  border=-s // 2)  # border to remove  即输出图片大小为img_size*img_size

    # cv2.imshow('mosaic_img', img4)  # debug显示mosaic
    # key = cv2.waitKey(0)

    return img4, labels4

def random_affine(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10, border=0):
    """随机旋转，缩放，平移以及错切"""
    # torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(.1, .1), scale=(.9, 1.1), shear=(-10, 10))
    # https://medium.com/uruvideo/dataset-augmentation-with-random-homographies-a8f4b44830d4
    # 这里可以参考我写的博文: https://blog.csdn.net/qq_37541097/article/details/119420860
    # targets = [cls, xyxy]

    # 最终输出的图像尺寸，等于img4.shape / 2，即从mosaic后两倍的img_size变回img_size*img_size
    height = img.shape[0] + border * 2
    width = img.shape[1] + border * 2

    # Rotation and Scale
    # 生成旋转以及缩放矩阵
    R = np.eye(3)  # 生成对角阵
    a = random.uniform(-degrees, degrees)  # 随机旋转角度  random.uniform(a, b)用于生成一个a到b之间的随机浮点数
    s = random.uniform(1 - scale, 1 + scale)  # 随机缩放因子
    R[:2] = cv2.getRotationMatrix2D(angle=a, center=(img.shape[1] / 2, img.shape[0] / 2), scale=s)

    # Translation
    # 生成平移矩阵
    T = np.eye(3)
    T[0, 2] = random.uniform(-translate, translate) * img.shape[0] + border  # x translation (pixels)
    T[1, 2] = random.uniform(-translate, translate) * img.shape[1] + border  # y translation (pixels)

    # Shear
    # 生成错切矩阵
    S = np.eye(3)
    S[0, 1] = math.tan(random.uniform(-shear, shear) * math.pi / 180)  # x shear (deg)
    S[1, 0] = math.tan(random.uniform(-shear, shear) * math.pi / 180)  # y shear (deg)

    # Combined rotation matrix
    M = S @ T @ R  # ORDER IS IMPORTANT HERE!!  numpy中的矩阵乘法可用@代替matmul
    if (border != 0) or (M != np.eye(3)).any():  # image changed
        # 进行仿射变化, 取M的2*3即可
        img = cv2.warpAffine(img, M[:2], dsize=(width, height), flags=cv2.INTER_LINEAR, borderValue=(114, 114, 114))

    # Transform label coordinates
    n = len(targets)
    if n:
        # warp points
        xy = np.ones((n * 4, 3))
        xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].reshape(n * 4, 2)  # x1y1左上, x2y2右下, x1y2左下, x2y1右上
        # [4*n, 3] -> [n, 8]
        xy = (xy @ M.T)[:, :2].reshape(n, 8)  # 将每个标签信息的4个坐标与仿射矩阵M相乘得到变换后的坐标，然后再变回(n, 8)

        # create new boxes
        # 对transform后的bbox进行修正(假设变换后的bbox变成了菱形，此时要修正成矩形)，修正方法：包围菱形的最小的矩形
        x = xy[:, [0, 2, 4, 6]]  # [n, 4]
        y = xy[:, [1, 3, 5, 7]]  # [n, 4]
        xy = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T  # [n, 4]

        # reject warped points outside of image
        # 对坐标进行裁剪，防止越界
        xy[:, [0, 2]] = xy[:, [0, 2]].clip(0, width)
        xy[:, [1, 3]] = xy[:, [1, 3]].clip(0, height)
        w = xy[:, 2] - xy[:, 0]  # 计算目标边界框的宽度和高度
        h = xy[:, 3] - xy[:, 1]

        # 计算调整后的每个box的面积
        area = w * h
        # 计算调整前的每个box的面积
        area0 = (targets[:, 3] - targets[:, 1]) * (targets[:, 4] - targets[:, 2])
        # 计算每个box的比例
        ar = np.maximum(w / (h + 1e-16), h / (w + 1e-16))  # aspect ratio
        # 选取长宽大于4个像素，且调整前后面积比例大于0.2，且比例小于10的box
        i = (w > 4) & (h > 4) & (area / (area0 * s + 1e-16) > 0.2) & (ar < 10)  # i为n维的bool值

        targets = targets[i]  # 选取满足543行条件的边界框
        targets[:, 1:5] = xy[i]  # 将选取出来的边界框的坐标替换成经仿射变换后的坐标

    return img, targets

def augment_hsv(img, h_gain=0.5, s_gain=0.5, v_gain=0.5):  # HSV:色度、饱和度、亮度增强
    # 这里可以参考我写的博文:https://blog.csdn.net/qq_37541097/article/details/119478023
    r = np.random.uniform(-1, 1, 3) * [h_gain, s_gain, v_gain] + 1  # random gains
    hue, sat, val = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
    dtype = img.dtype  # uint8

    x = np.arange(0, 256, dtype=np.int16)
    lut_hue = ((x * r[0]) % 180).astype(dtype)
    lut_sat = np.clip(x * r[1], 0, 255).astype(dtype)
    lut_val = np.clip(x * r[2], 0, 255).astype(dtype)

    img_hsv = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val))).astype(dtype)
    cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR, dst=img)  # no return needed

def letterbox(img: np.ndarray,
              new_shape=(416, 416),
              color=(114, 114, 114),
              auto=True,
              scale_fill=False,
              scale_up=True):
    """
    将图片缩放调整到指定大小
    :param img:
    :param new_shape:
    :param color:
    :param auto:
    :param scale_fill:
    :param scale_up:
    :return:
    """

    shape = img.shape[:2]  # [h, w]
    if isinstance(new_shape, int):
        new_shape = (new_shape, new_shape)

    # scale ratio (new / old)
    r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
    if not scale_up:  # only scale down, do not scale up (for better test mAP) 对于大于指定输入大小的图片进行缩放,小于的不变
        r = min(r, 1.0)

    # compute padding
    ratio = r, r  # width, height ratios
    new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
    dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]  # wh padding
    if auto:  # minimun rectangle 保证原图比例不变，将图像最大边缩放到指定大小
        # 这里的取余操作可以保证padding后的图片是32的整数倍
        dw, dh = np.mod(dw, 32), np.mod(dh, 32)  # wh padding
    elif scale_fill:  # stretch 简单粗暴的将图片缩放到指定尺寸
        dw, dh = 0, 0
        new_unpad = new_shape
        ratio = new_shape[0] / shape[1], new_shape[1] / shape[0]  # wh ratios

    dw /= 2  # divide padding into 2 sides 将padding分到上下，左右两侧
    dh /= 2

    # shape:[h, w]  new_unpad:[w, h]
    if shape[::-1] != new_unpad:
        img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
    top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))  # 计算上下两侧的padding
    left, right = int(round(dw - 0.1)), int(round(dw + 0.1))  # 计算左右两侧的padding

    img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)  # add border
    return img, ratio, (dw, dh)

def create_folder(path="./new_folder"):
    # Create floder
    if os.path.exists(path):
        shutil.rmtree(path)  # dalete output folder
    os.makedirs(path)  # make new output folder


import logging
import shutil
import os
import subprocess

import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.scrubber import EventScrubber, DEFAULT_DENYLIST

from benefits import VERSION

logger = logging.getLogger(__name__)

SENTRY_ENVIRONMENT = os.environ.get("SENTRY_ENVIRONMENT", "local")
SENTRY_CSP_REPORT_URI = None

def git_available():
    return bool(shutil.which("git"))

# https://stackoverflow.com/a/24584384/358804
def is_git_directory(path="."):
    with open(os.devnull, "w") as dev_null:
        return subprocess.call(["git", "-C", path, "status"], stderr=dev_null, stdout=dev_null) == 0

# https://stackoverflow.com/a/21901260/358804
def get_git_revision_hash():
    return subprocess.check_output(["git", "rev-parse", "HEAD"]).decode("ascii").strip()

def get_sha_file_path():
    current_file = os.path.dirname(os.path.abspath(__file__))
    return os.path.join(current_file, "..", "static", "sha.txt")

def get_sha_from_file():
    sha_path = get_sha_file_path()
    if os.path.isfile(sha_path):
        with open(sha_path) as f:
            return f.read().strip()
    else:
        return None

def get_release() -> str:
    """Returns the first available: the SHA from Git, the value from sha.txt, or the VERSION."""

    if git_available() and is_git_directory():
        return get_git_revision_hash()
    else:
        sha = get_sha_from_file()
        if sha:
            return sha
        else:
            # one of the above *should* always be available, but including this just in case
            return VERSION

def get_denylist():
    # custom denylist
    denylist = DEFAULT_DENYLIST + ["sub", "name"]
    return denylist

def get_traces_sample_rate():
    try:
        rate = float(os.environ.get("SENTRY_TRACES_SAMPLE_RATE", "0.0"))
        if rate < 0.0 or rate > 1.0:
            logger.warning("SENTRY_TRACES_SAMPLE_RATE was not in the range [0.0, 1.0], defaulting to 0.0")
            rate = 0.0
        else:
            logger.info(f"SENTRY_TRACES_SAMPLE_RATE set to: {rate}")
    except ValueError:
        logger.warning("SENTRY_TRACES_SAMPLE_RATE did not parse to float, defaulting to 0.0")
        rate = 0.0

    return rate

def configure():
    SENTRY_DSN = os.environ.get("SENTRY_DSN")
    if SENTRY_DSN:
        release = get_release()
        logger.info(f"Enabling Sentry for environment '{SENTRY_ENVIRONMENT}', release '{release}'...")

        # https://docs.sentry.io/platforms/python/configuration/
        sentry_sdk.init(
            dsn=SENTRY_DSN,
            integrations=[
                DjangoIntegration(),
            ],
            traces_sample_rate=get_traces_sample_rate(),
            environment=SENTRY_ENVIRONMENT,
            release=release,
            in_app_include=["benefits"],
            # send_default_pii must be False (the default) for a custom EventScrubber/denylist
            # https://docs.sentry.io/platforms/python/data-management/sensitive-data/#event_scrubber
            send_default_pii=False,
            event_scrubber=EventScrubber(denylist=get_denylist()),
        )

        # override the module-level variable when configuration happens, if set
        global SENTRY_CSP_REPORT_URI
        SENTRY_CSP_REPORT_URI = os.environ.get("SENTRY_REPORT_URI", "")
    else:
        logger.info("SENTRY_DSN not set, so won't send events")


from django.contrib import admin
from import_export import resources
from import_export.admin import ImportExportModelAdmin
from recipes.models import Cart, Ingredient, IngredientAmount, Recipe, Tag
from users.models import Follow, User

class UserResource(resources.ModelResource):
    class Meta:
        model = User
        fields = (
            'id',
            'username',
            'email',
            'first_name',
            'last_name',
            'password',
        )

class TagResource(resources.ModelResource):

    class Meta:
        model = Tag
        fields = (
            'color',
            'name',
            'slug',
            'id'
        )

class IngredientAmountResource(resources.ModelResource):

    class Meta:
        model = IngredientAmount
        fields = (
            'recipe',
            'ingredient',
            'amount',
        )

@admin.register(IngredientAmount)
class IngredientAmountAdmin(ImportExportModelAdmin):
    resource_classes = [IngredientAmountResource]
    list_display = (
        'recipe',
        'ingredient',
        'amount',
    )

class CartResource(resources.ModelResource):

    class Meta:
        model = Cart
        fields = (
            'recipe',
            'user',
            'date_added',
        )

@admin.register(Cart)
class CartAdmin(ImportExportModelAdmin):
    resource_classes = [CartResource]
    list_display = (
        'recipe',
        'user',
        'date_added',
    )

@admin.register(Tag)
class TagAdmin(ImportExportModelAdmin):
    resource_classes = [TagResource]
    list_display = (
        'color',
        'name',
        'slug',
        'id'
    )

class RecipeResource(resources.ModelResource):

    class Meta:
        model = Recipe
        fields = (
            'id',
            'name',
            'author',
            'tags',
            'ingredients',
            'slug',
            'pub_date',
            'image',
            'text',
            'cooking_time',
        )

@admin.register(Recipe)
class RecipeAdmin(ImportExportModelAdmin):
    resource_classes = [RecipeResource]
    list_display = (
        'name',
        'author',
        'favourite_count',
    )
    list_filter = ('name', 'author', 'tags')

    def favourite_count(self, obj):
        return obj.in_favourites.count()

class IngredientResource(resources.ModelResource):

    class Meta:
        model = Ingredient
        fields = (
            'id',
            'name',
            'measurement_unit',
        )

@admin.register(Ingredient)
class IngredientsAdmin(ImportExportModelAdmin):
    resource_classes = [IngredientResource]
    list_display = (
        'name',
        'measurement_unit',
    )
    list_filter = ('name',)

@admin.register(User)
class UserAdmin(ImportExportModelAdmin):
    resource_classes = [UserResource]
    list_display = (
        'username',
        'email',
        'first_name',
        'last_name',
        'password',
    )
    list_filter = ('username', 'email')

class FollowResource(resources.ModelResource):

    class Meta:
        model = Follow
        firleds = (
            'author',
            'user',
        )

@admin.register(Follow)
class FollowAdmin(ImportExportModelAdmin):
    resource_classes = [FollowResource]
    list_display = (
        'author',
        'user',
    )


import torch
import torch.nn as nn
from torch import optim
import torch.nn.functional as F
import numpy as np
import utils

class TemporalVAE(nn.Module):

    def __init__(self, input_size, hidden_size, latent_size, k_step, activation_func='tanh'):
        super(TemporalVAE, self).__init__()

        self.k_step = k_step
        self.input_size = input_size
        self.latent_size = latent_size

        # Encoder
        self.f_enc1 = nn.Linear(input_size, hidden_size)
        self.f_mu = nn.Linear(hidden_size, latent_size)
        self.f_logvar = nn.Linear(hidden_size, latent_size)

        # Decoder
        self.f_dec1 = nn.Linear(latent_size, hidden_size)
        self.f_dec2 = nn.Linear(hidden_size, input_size)

        # Transition Function
        self.f_trainsition = nn.Linear(latent_size, latent_size)
        # self.f_trainsition2 = nn.Linear(latent_size, latent_size)

        # Activation Function
        self.act_func = getattr(torch, activation_func)

    def encode(self, state):
        hidden = self.act_func(self.f_enc1(state))
        return self.f_mu(hidden), self.f_logvar(hidden)

    def predict(self, prev_states):
        next_states = self.f_trainsition(prev_states)
        # next_state = self.f_trainsition2(hidden)
        return next_states

    def decode(self, hidden):
        hidden = self.act_func(self.f_dec1(hidden))
        decoded = self.f_dec2(hidden)
        # decoded = torch.sigmoid(decoded)
        return decoded

    def sample(self, mu, logvar):
        std = torch.exp(0.5 * logvar)
        eps = torch.randn_like(std)
        return mu + eps * std

    def forward(self, states):

        # Reconstruct the trajectory
        mu, logvar = self.encode(states[:, 0:2])
        latent = self.sample(mu, logvar)
        recon = self.decode(latent)

        z_next_pred = self.predict(latent)

        mu_next, logvar_next = self.encode(states[:, 3:5])
        z_next_sampled = self.sample(mu_next, logvar_next)

        return recon, mu, logvar, z_next_sampled, z_next_pred

    def loss_func(self, recon, states, mu, logvar, z_next_sampled, z_next_pred):
        MSE_REC = F.mse_loss(input=recon, target=states[:, 0:2], reduction='sum')
        MSE_PRED = F.mse_loss(input=z_next_pred, target=z_next_sampled, reduction='sum')
        KLD = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())

        return MSE_REC, MSE_PRED, KLD

def train_epoch(model, optimizer, beta, train_data_loader, device):
    model.train()
    total_loss, recon_loss, pred_loss, kld_loss = 0, 0, 0, 0
    for states in train_data_loader:
        states_batch = states.to(device)

        optimizer.zero_grad()

        recon, mu, logvar, z_next_sampled, z_next_pred = model(states_batch)
        MSE_REC, MSE_PRED, KLD = model.loss_func(recon, states_batch, mu, logvar, z_next_sampled, z_next_pred)
        # loss = MSE_REC + beta * KLD + MSE_PRED
        loss = MSE_REC + MSE_PRED
        loss.backward()
        total_loss += loss.item()
        recon_loss += MSE_REC.item()
        pred_loss += MSE_PRED.item()
        kld_loss += KLD.item()

        optimizer.step()

    total_loss /= len(train_data_loader.dataset)
    recon_loss /= len(train_data_loader.dataset)
    pred_loss /= len(train_data_loader.dataset)
    kld_loss /= len(train_data_loader.dataset)

    loss_dict = {'total': total_loss, 'recon': recon_loss,
                 'pred': pred_loss, 'kld': kld_loss}

    return loss_dict

def evaluate_epoch(model, beta, valid_data_loader, device):
    model.eval()
    total_loss, recon_loss, pred_loss, kld_loss = 0, 0, 0, 0
    with torch.no_grad():
        for states in valid_data_loader:
            states_batch = states.to(device)

            recon, mu, logvar, z_next_sampled, z_next_pred = model(states_batch)
            MSE_REC, MSE_PRED, KLD = model.loss_func(recon, states_batch, mu, logvar, z_next_sampled, z_next_pred)
            # loss = MSE_REC + MSE_PRED + beta * KLD
            loss = MSE_REC + MSE_PRED

            total_loss += loss.item()
            recon_loss += MSE_REC.item()
            pred_loss += MSE_PRED.item()
            kld_loss += KLD.item()

        total_loss /= len(valid_data_loader.dataset)
        recon_loss /= len(valid_data_loader.dataset)
        pred_loss /= len(valid_data_loader.dataset)
        kld_loss /= len(valid_data_loader.dataset)

        loss_dict = {'total': total_loss, 'recon': recon_loss,
                     'pred': pred_loss, 'kld': kld_loss}

    return loss_dict

def train(model, config, train_data_loader, valid_data_loader, checkpoint_path, loss_path, device):
    optimizer = optim.Adam(params=model.parameters(), lr=config.lr)
    print('Starting training...')
    best_eval_loss = 100000.0
    # betas = np.linspace(0, 0.1, config.num_epochs)
    epoch_loss = np.zeros((config.num_epochs, 8))

    for epoch_i in range(config.num_epochs):
        train_loss_dict = train_epoch(model, optimizer, config.beta, train_data_loader, device)
        valid_loss_dict = evaluate_epoch(model, config.beta, valid_data_loader, device)

        print('====> Epoch: {}/{}'.format(epoch_i+1, config.num_epochs))
        print('====> Train loss:     {:.7f} Eval Loss: {:.7f} '.format(train_loss_dict['total'],
                                                                       valid_loss_dict['total']))

        print('====> Train MSE Loss: {:.7f} MSE T Loss {:.7f} KLD Loss:  {:.7f} '.format(train_loss_dict['recon'],
                                                                                train_loss_dict['pred'],
                                                                                train_loss_dict['kld']))

        print('====> Eval  MSE Loss: {:.7f} MSE T Loss {:.7f} KLD Loss:  {:.7f} '.format(valid_loss_dict['recon'],
                                                                                valid_loss_dict['pred'],
                                                                                valid_loss_dict['kld']))

        epoch_loss[epoch_i] = np.hstack((list(train_loss_dict.values()), list(valid_loss_dict.values())))

        is_best = valid_loss_dict['total'] <= best_eval_loss
        if is_best:
            print('====> New best checkpoint!')
            best_eval_loss = valid_loss_dict['total']

        if config.save:
            utils.save_checkpoint(state=model.state_dict(),
                                  checkpoint_path=checkpoint_path,
                                  is_best=is_best,
                                  save_only_best=False)

        print('----------------------------------------------------------------------------')

    if config.save:
        with open(loss_path, 'wb') as f:
            np.savetxt(f, epoch_loss, delimiter=',')


import re
import shutil
import os
import subprocess
import itertools
import collections
import argparse
import multiprocessing

compilers = {
    'g++': (4.9, 6),
    'clang++': (3.6, 3.7, 3.8),
}

settings = {
    'osize': '-g -Os -fPIC',
    'o1':    '-g -O1',
    'o2':    '-g -O2',
    'o3':    '-g -O3',
    'o2n':   '-g -O2 -march=native',
    'o3n':   '-g -O3 -march=native',
}

config = collections.namedtuple('config', 'comp version name cxxflags')
def get_configs():
    versioned_comps = [(comp,v) for comp,versions in compilers.items() for v in versions]
    all_configs = itertools.product(versioned_comps, settings.items())
    return [config(c[0][0], c[0][1], c[1][0], c[1][1]) for c in all_configs]

root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))

def _cmake_c(c):
    dirname = os.path.join(root, 'builds', c.comp, str(c.version), c.name)
    if not os.path.isdir(dirname):
        os.makedirs(dirname)

    cmake_cxxflags = '-DCMAKE_CXX_FLAGS=%s' % c.cxxflags
    cmake_cxx = '-DCMAKE_CXX_COMPILER=%s-%s' % (c.comp, str(c.version))
    subprocess.check_call(['rm', '-rf'], cwd=dirname)
    subprocess.check_call(['cmake', cmake_cxx, cmake_cxxflags, root], cwd=dirname)

def cmake(jobs):
    bdir = os.path.join(root, 'builds')
    if not os.path.isdir(bdir):
        os.mkdir(bdir)
    p = multiprocessing.Pool(jobs)
    p.map(_cmake_c, get_configs())
    p.close()

def _make_c(c):
    dirname = os.path.join(root, 'builds', c.comp, str(c.version), c.name)
    subprocess.check_call(['make'], cwd=dirname)

def make(jobs):
    p = multiprocessing.Pool(jobs)
    p.map(_make_c, get_configs())
    p.close()

_r = re.compile('Ran [0-9]+ iterations in [0-9\.]+ \[s\] \(([0-9\.]+) \[ms\] per iteration\)')
def benchmark(args=None):
    for c in get_configs():
        dirname = os.path.join('builds', c.comp, str(c.version), c.name)
        cmdline = ['./profit-cli']
        if args:
            cmdline += args
        else:
            cmdline += ['-w', '400', '-H', '400', ]
            cmdline += ['-p', 'sersic:xcen=201:ycen=201:mag=15.87:re=4.65:nser=4.7:ang=-21:axrat=0.74:box=0']
            cmdline += ['-p', 'sersic:xcen=201:ycen=201:mag=16.63:re=41:nser=1:axrat=0.29:box=0']
            cmdline += ['-i', '100']
        out = subprocess.check_output(cmdline, cwd=dirname).strip()
        ms_per_iter = _r.match(out).group(1)
        print('%s, %s, %s, %s' % (c.comp, str(c.version), c.name, ms_per_iter))

if __name__ == '__main__':

    parser = argparse.ArgumentParser(description="A tool to run commands through different builds")
    g = parser.add_mutually_exclusive_group(required=True)
    g.add_argument('-c', '--cmake', help="Runs CMake to prepare the build", action="store_true")
    g.add_argument('-m', '--make', help="Runs make in the builds", action="store_true")
    g.add_argument('-b', '--benchmark', help="Runs a benchmark in the builds. -j has no effect", action="store_true")
    parser.add_argument('-j', help="Numbers of jobs to run in parallel (processes)", type=int, dest="jobs", default=1)
    parser.add_argument('args', help="Arguments to use for benchmarking (optional)", nargs=argparse.REMAINDER)

    args = parser.parse_args()
    if args.cmake:
        cmake(args.jobs)
    if args.make:
        make(args.jobs)
    elif args.benchmark:
        # -- is there? it shouldn't...
        if len(args.args) > 0 and args.args[0] == '--':
            args.args = args.args[1:]
        benchmark(args.args)


"""Command line tool for creating premis events"""
from __future__ import unicode_literals

import os
import sys
from uuid import uuid4

import click
import six

import mets
import premis
import xml_helpers.utils
from siptools.utils import MdCreator, encode_id, encode_path
from siptools.xml.premis import PREMIS_EVENT_OUTCOME_TYPES, PREMIS_EVENT_TYPES

click.disable_unicode_literals_warning = True

def _list2str(lst):
    """Create a human readable list of words from list of strings.

    :param lst: list of strings
    :returns: list formatted as single string
    """
    first_words = ['"{}"'.format(string) for string in lst[:-1]]
    last_word = '"{}"'.format(lst[-1])
    return ', '.join(first_words) + ', and ' + last_word

@click.command()
@click.argument('event_type',
                type=click.Choice(PREMIS_EVENT_TYPES))
@click.argument('event_datetime', required=True,
                type=str)
@click.option('--workspace',
              type=click.Path(exists=True),
              default='./workspace',
              metavar='<WORKSPACE PATH>',
              help=("Directory where files are created. Defaults "
                    "to ./workspace/"))
@click.option('--base_path', type=click.Path(exists=True), default='.',
              metavar='<BASE PATH>',
              help=("Source base path of event_target. If used, give "
                    "event_target in relation to this base path."))
@click.option('--event_target',
              type=str,
              metavar='<EVENT TARGET PATH>',
              help=('Target for the event. Default is the root of '
                    'digital objects.'))
@click.option('--event_detail',
              type=str, required=True,
              metavar='<EVENT DETAIL>',
              help='Short information about the event')
@click.option('--event_outcome',
              type=click.Choice(PREMIS_EVENT_OUTCOME_TYPES),
              required=True,
              metavar='<EVENT OUTCOME>',
              help=('Event outcome type. Possible values are: ' +
                    _list2str(PREMIS_EVENT_OUTCOME_TYPES)))
@click.option('--event_outcome_detail',
              type=str,
              metavar='<EVENT OUTCOME DETAIL>',
              help='Detailed information about the event outcome.')
@click.option('--agent_name',
              required='--agent_type' in sys.argv,
              type=str,
              metavar='<AGENT NAME>',
              help='Agent name')
@click.option('--agent_type',
              required='--agent_name' in sys.argv,
              type=str,
              metavar='<AGENT TYPE>',
              help='Agent type.')
@click.option('--stdout',
              is_flag=True,
              help='Print output to stdout')
def main(event_type, event_datetime, event_detail, event_outcome,
         event_outcome_detail, workspace, base_path, agent_name, agent_type,
         stdout, event_target):
    """The script creates provenance metadata for the package. The metadata
    contains event and, if given, also agent of the event.

    \b
    EVENT_TYPE: Type of the event.
    EVENT_DATETIME: Timestamp of the event.
    """
    premis_event(event_type, event_datetime, event_detail, event_outcome,
                 event_outcome_detail, workspace, base_path, agent_name,
                 agent_type, stdout, event_target)

    return 0

def premis_event(event_type, event_datetime, event_detail, event_outcome,
                 event_outcome_detail=None, workspace="./workspace",
                 base_path=".", agent_name=None, agent_type=None, stdout=False,
                 event_target=None):
    """The script creates provenance metadata for the package. The metadata
    contains event and, if given, also agent of the event.
    """
    (directory, event_file) = event_target_path(base_path, event_target)

    if agent_name or agent_type:
        agent_identifier = six.text_type(uuid4())
        agent = create_premis_agent(agent_name,
                                    agent_type, agent_identifier)

        agent_creator = PremisCreator(workspace)
        agent_creator.add_md(agent, event_file, directory=directory)
        agent_creator.write(mdtype="PREMIS:AGENT", stdout=stdout)

        if stdout:
            print(xml_helpers.utils.serialize(agent).decode("utf-8"))
    else:
        agent_identifier = None

    event = create_premis_event(
        event_type,
        event_datetime,
        event_detail,
        event_outcome,
        event_outcome_detail,
        agent_identifier
    )

    creator = PremisCreator(workspace)
    creator.add_md(event, event_file, directory=directory)
    creator.write(mdtype="PREMIS:EVENT", stdout=stdout)

    if stdout:
        print(xml_helpers.utils.serialize(event).decode("utf-8"))

def event_target_path(base_path, event_target=None):
    """Returns the path to the event_target based on the base_path and
    event_target. If event_target is None, the event concerns the whole
    package.

    :returns: a tuple of directory and event_file.
    """
    event_file = None
    directory = None

    # If the given target path is an absolute path and base_path is
    # current path (i.e. not given), relpath will return ../../..
    # sequences, if current path is not part of the absolute path. In
    # such case we will use the absolute path for eventpath and omit
    # base_path relation.
    if event_target:
        if base_path not in ['.']:
            eventpath = os.path.normpath(os.path.join(base_path, event_target))
        else:
            eventpath = event_target

        if os.path.isdir(eventpath):
            directory = os.path.normpath(event_target)
        elif os.path.isfile(eventpath):
            event_file = event_target
        else:
            raise IOError
    else:
        directory = '.'

    return (directory, event_file)

class PremisCreator(MdCreator):
    """Subclass of MdCreator, which generates PREMIS event
    or agent metadata.
    """

    def write(self, mdtype="PREMIS", mdtypeversion="2.3",
              section="digiprovmd", stdout=False):
        super(PremisCreator, self).write(
            mdtype=mdtype, mdtypeversion=mdtypeversion, section=section)

def create_premis_agent(agent_name, agent_type, agent_identifier):
    """Creates METS digiprovMD element that contains PREMIS agent element with
    unique identifier.

    :param agent_name: content of PREMIS agentName element
    :param agent_type: content of PREMIS agentType element
    :param agent_identifier: content of PREMIS agentIdentifierValue element
    :returns: PREMIS event XML element
    """
    agent_identifier = premis.identifier(
        identifier_type='UUID',
        identifier_value=agent_identifier, prefix='agent'
    )
    premis_agent = premis.agent(agent_identifier, agent_name, agent_type)

    return premis_agent

def create_premis_event(event_type, event_datetime, event_detail,
                        event_outcome, event_outcome_detail, agent_identifier):
    """Creates METS digiprovMD element that contains PREMIS event element.
    Linking agent identifier element is added to PREMIS event element, if agent
    identifier is provided as parameter.

    :param event_type: Event type
    :param event_datetime: Event time
    :param event_detail: Event details
    :param event_outcome: Event outcome ("success" or "failure")
    :param event_outcome_detail: Event outcome description
    :param agent_identifier: PREMIS agent identifier or ``None``
    :returns: PREMIS event XML element
    """
    event_identifier = premis.identifier(
        identifier_type='UUID',
        identifier_value=six.text_type(uuid4()),
        prefix='event'
    )

    premis_event_outcome = premis.outcome(event_outcome,
                                          event_outcome_detail)

    child_elements = [premis_event_outcome]

    # Create linkingAgentIdentifier element if agent identifier is provided
    if agent_identifier is not None:
        linking_agent_identifier = premis.identifier(
            identifier_type='UUID',
            identifier_value=agent_identifier,
            prefix='linkingAgent'
        )
        child_elements.append(linking_agent_identifier)

    premis_event_elem = premis.event(event_identifier, event_type,
                                     event_datetime, event_detail,
                                     child_elements=child_elements)

    return premis_event_elem

def create_premis_agent_file(workspace, event_type, agent_name, agent_type,
                             agent_identifier, event_target=None):
    """Creates `<event_type>-agent.xml` file. If path to target file is given
    as `event_target` parameter, the URL-encoded path is used as filename
    prefix. The file is METS XML file that contains PREMIS agent element inside
    digiprovMD element. The ID attribute of digiprovMD is hashed from the
    filename.

    :param workspace: path to directory where file is created
    :param event_type: event type (for filename)
    :param agent_name: PREMIS agentName
    :param agent_type: PREMIS agentType
    :param agent_identifier: PREMIS agentIdentifierValue
    :param event_target: event target file (for filename)
    :returns: output file path and METS XML element object
    """
    output_filename = '%s-agent-amd.xml' % (event_type)
    if event_target:
        output_filename = '%s-%s' % (event_target, output_filename)
    output_filename = encode_path(output_filename)

    agent_id = encode_id(output_filename)

    premis_agent = create_premis_agent(agent_name,
                                       agent_type,
                                       agent_identifier)

    agent_mets = _create_mets(premis_agent, agent_id, 'PREMIS:AGENT')
    _write_mets(agent_mets, os.path.join(workspace, output_filename))

    return (os.path.join(workspace, output_filename),
            agent_mets)

def create_premis_event_file(workspace, event_type, event_datetime,
                             event_detail, event_outcome, event_outcome_detail,
                             event_target=None, agent_identifier=None):
    """Creates `<event_type>-event.xml` file. If path to target file is given
    as `event_target` parameter, the URL-encoded path is used as filename
    prefix. The file is METS XML file that contains PREMIS event element inside
    digiprovMD element. The ID attribute of digiprovMD is hashed from the
    filename.

    :param workspace: path to directory where file is created
    :param event_type: PREMIS eventType
    :param event_datetime: PREMIS eventDateTime
    :param event_detail: PREMIS eventDetail
    :param event_outcome: PREMIS eventOutcome
    :param event_outcome_detail: PREMIS eventOutcomeDetail
    :param agent_identifier: PREMIS linkingAgentIdentifierValue
    :param event_target: event target file (for filename)
    :returns: output file path and METS XML element object
    """
    output_filename = '%s-event-amd.xml' % event_type
    if event_target:
        output_filename = '%s-%s' % (event_target, output_filename)
    output_filename = encode_path(output_filename)

    event_id = encode_id(output_filename)

    premis_event_elem = create_premis_event(
        event_type, event_datetime, event_detail,
        event_outcome, event_outcome_detail, agent_identifier
    )

    event_mets = _create_mets(premis_event_elem, event_id, 'PREMIS:EVENT')
    _write_mets(event_mets, os.path.join(workspace, output_filename))

    return (os.path.join(workspace, output_filename),
            event_mets)

def _write_mets(mets_element, output_file):
    """Write METS XML element to file.

    :param mets_element: METS XML element
    :param output: output file path
    :returns: ``None``
    """

    if not os.path.exists(os.path.dirname(output_file)):
        os.makedirs(os.path.dirname(output_file))

    with open(output_file, 'wb+') as output:
        output.write(xml_helpers.utils.serialize(mets_element))

def _create_mets(premis_element, digiprovmd_id, mdtype):
    """Creates a METS XML element that contains PREMIS element

    :param premis_element: PREMIS element
    :param digiprovmd_id: ID attribute of digiprovMD element
    :param mdtype: MDTYPE of mdWrap element
    :returns: METS XML element
    """
    xmldata = mets.xmldata(child_elements=[premis_element])
    mdwrap = mets.mdwrap(mdtype, '2.3', child_elements=[xmldata])
    digiprovmd = mets.digiprovmd(digiprovmd_id, child_elements=[mdwrap])
    amdsec = mets.amdsec(child_elements=[digiprovmd])
    _mets = mets.mets(child_elements=[amdsec])

    return _mets

if __name__ == '__main__':
    RETVAL = main()  # pylint: disable=no-value-for-parameter
    sys.exit(RETVAL)


from __future__ import division

from datetime import date, datetime

from dateutil.relativedelta import relativedelta

from indico.core.db import db
from indico.modules.rb.models.locations import Location
from indico.modules.rb.models.reservation_occurrences import ReservationOccurrence
from indico.modules.rb.models.reservations import Reservation
from indico.util.date_time import iterdays

def calculate_rooms_bookable_time(rooms, start_date=None, end_date=None):
    if end_date is None:
        end_date = date.today() - relativedelta(days=1)
    if start_date is None:
        start_date = end_date - relativedelta(days=29)
    working_time_per_day = sum((datetime.combine(date.today(), end) - datetime.combine(date.today(), start)).seconds
                               for start, end in Location.working_time_periods)
    working_days = sum(1 for __ in iterdays(start_date, end_date, skip_weekends=True))
    return working_days * working_time_per_day * len(rooms)

def calculate_rooms_booked_time(rooms, start_date=None, end_date=None):
    if end_date is None:
        end_date = date.today() - relativedelta(days=1)
    if start_date is None:
        start_date = end_date - relativedelta(days=29)
    # Reservations on working days
    reservations = Reservation.find(Reservation.room_id.in_(r.id for r in rooms),
                                    db.extract('dow', ReservationOccurrence.start_dt).between(1, 5),
                                    ReservationOccurrence.start_dt >= start_date,
                                    ReservationOccurrence.end_dt <= end_date,
                                    ReservationOccurrence.is_valid,
                                    _join=ReservationOccurrence)

    rsv_start = db.cast(ReservationOccurrence.start_dt, db.TIME)
    rsv_end = db.cast(ReservationOccurrence.end_dt, db.TIME)
    slots = ((db.cast(start, db.TIME), db.cast(end, db.TIME)) for start, end in Location.working_time_periods)

    # this basically handles all possible ways an occurrence overlaps with each one of the working time slots
    overlaps = sum(db.case([
                ((rsv_start < start) & (rsv_end > end), db.extract('epoch', end - start)),
                ((rsv_start < start) & (rsv_end > start) & (rsv_end <= end), db.extract('epoch', rsv_end - start)),
                ((rsv_start >= start) & (rsv_start < end) & (rsv_end > end), db.extract('epoch', end - rsv_start)),
                ((rsv_start >= start) & (rsv_end <= end), db.extract('epoch', rsv_end - rsv_start))
            ], else_=0) for start, end in slots)

    return reservations.with_entities(db.func.sum(overlaps)).scalar() or 0

def calculate_rooms_occupancy(rooms, start=None, end=None):
    bookable_time = calculate_rooms_bookable_time(rooms, start, end)
    booked_time = calculate_rooms_booked_time(rooms, start, end)
    return booked_time / bookable_time if bookable_time else 0

def compose_rooms_stats(rooms):
    reservations = Reservation.find(Reservation.room_id.in_(r.id for r in rooms))
    return {
        'active': {
            'valid': reservations.filter(Reservation.is_valid, ~Reservation.is_archived).count(),
            'pending': reservations.filter(Reservation.is_pending, ~Reservation.is_archived).count(),
            'cancelled': reservations.filter(Reservation.is_cancelled, ~Reservation.is_archived).count(),
            'rejected': reservations.filter(Reservation.is_rejected, ~Reservation.is_archived).count(),
        },
        'archived': {
            'valid': reservations.filter(Reservation.is_valid, Reservation.is_archived).count(),
            'pending': reservations.filter(Reservation.is_pending, Reservation.is_archived).count(),
            'cancelled': reservations.filter(Reservation.is_cancelled, Reservation.is_archived).count(),
            'rejected': reservations.filter(Reservation.is_rejected, Reservation.is_archived).count()
        }
    }