from django.db.models import Q
from django.http import HttpResponse
from django.shortcuts import (
    render,
    get_object_or_404,
    redirect
    )
from django.contrib.auth.decorators import login_required
from django.urls import reverse

from .models import (
    Planning,
    Tag_Subregion,
    Tag_Project,
    Tag_Language,
    Tag_Region,
    Comment
    )

from .forms import (
    PlanningCreateForm,
    CommentForm
    )

from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage

# 목록
def detail_list(request):
    if request.method == "POST":
        selected_region_all = request.POST.get('city_all')
        selected_city_pk = [] + request.POST.getlist('city')
        selected_project_pk = [] + request.POST.getlist('project')
        selected_language_pk = [] +request.POST.getlist('language')

        url = reverse('planningboard:list')
        if selected_region_all:
            url += '?selected_region_all=' + str(selected_region_all)
        else:
            url += '?selected_city_pk=' + str(selected_city_pk)

        if request.POST.get('language_all'):
            url += '&selected_language_pk=' + str(list(map(lambda x:x.get('pk'), Tag_Language.objects.values('pk'))))
        else:
            url += '&selected_language_pk=' + str(selected_language_pk)

        if request.POST.get('project_all'):
            url += '&selected_project_pk=' + str(list(map(lambda x: x.get('pk'),Tag_Project.objects.values('pk'))))
        else:
            url += '&selected_project_pk=' + str(selected_project_pk)

        return redirect(url)

    selected_language_pk = eval(request.GET.get('selected_language_pk') or '[]')
    selected_project_pk = eval(request.GET.get('selected_project_pk') or '[]')
    detail_list = Planning.objects.filter()

    if selected_language_pk:
        node = Q(language__pk=selected_language_pk[0])
        for pk in selected_language_pk:
            node |= Q(language__pk=pk)
        detail_list = detail_list.filter(node).distinct()

    if selected_project_pk:
        node = Q(project__pk=selected_project_pk[0])
        for pk in selected_project_pk:
            node |= Q(project__pk=pk)
        detail_list = detail_list.filter(node).distinct()

    if request.GET.get('selected_region_all'):
        selected_region_all = request.GET.get('selected_region_all')
        detail_list = detail_list.filter(region__pk=selected_region_all)
        selected_province_pk =  selected_region_all
        selected_city_pk = []
    else:
        selected_city_pk = eval(request.GET.get('selected_city_pk') or '[]')
        if len(selected_city_pk) == 0:
            selected_province_pk = 0
        else:
            node = Q(subregion__pk=selected_city_pk[0])
            for pk in selected_city_pk:
                node |= Q(subregion__pk=pk)
            detail_list = detail_list.filter(node).distinct()
            selected_province_pk = Tag_Subregion.objects.get(pk=selected_city_pk[0]).region.pk
        selected_region_all = None

    detail_list = detail_list.order_by('-updated_at')
    PAGE_ROW_COUNT = 6
    PAGE_DISPLAY_COUNT = 5

    paginator = Paginator(detail_list, PAGE_ROW_COUNT)
    pageNum = request.GET.get('pageNum')
    totalPageCount = paginator.num_pages
    try:
        detail_list = paginator.page(pageNum)
    except PageNotAnInteger:
        detail_list = paginator.page(1)
        pageNum = 1
    except EmptyPage:
        detail_list = paginator.page(paginator.num_pages)
        pageNum = paginator.num_pages

    pageNum = int(pageNum)

    startPageNum = int(1+((pageNum-1)/PAGE_DISPLAY_COUNT)*PAGE_DISPLAY_COUNT)
    endPageNum = int(startPageNum+PAGE_DISPLAY_COUNT-1)
    if totalPageCount < endPageNum:
        endPageNum = totalPageCount

    bottomPages = range(startPageNum, endPageNum+1)

    ctx = {
        'detail_list': detail_list,
        'province_list': Tag_Region.objects.all(),
        'region_list': Tag_Subregion.objects.all(),
        'project_list': Tag_Project.objects.all(),
        'language_list': Tag_Language.objects.all(),
        'selected_province_pk': selected_province_pk,
        'selected_city_pk': selected_city_pk,
        'selected_region_all': selected_region_all,
        'selected_language_pk': selected_language_pk,
        'selected_project_pk': selected_project_pk,
        'pageNum': pageNum,
        'bottomPages': bottomPages,
        'totalPageCount': totalPageCount,
        'startPageNum': startPageNum,
        'endPageNum': endPageNum,
    }
    return render(request, 'planning_list.html', ctx)

def detail_list_html(request):
    selected_language_pk = eval(request.GET.get('selected_language_pk') or '[]')
    selected_project_pk = eval(request.GET.get('selected_project_pk') or '[]')
    detail_list = Planning.objects.filter()
    if selected_language_pk:
        node = Q(language__pk=selected_language_pk[0])
        for pk in selected_language_pk:
            node |= Q(language__pk=pk)
            detail_list = detail_list.filter(node).distinct()

    if selected_project_pk:
        node = Q(project__pk=selected_project_pk[0])
        for pk in selected_project_pk:
            node |= Q(project__pk=pk)
            detail_list = detail_list.filter(node).distinct()

    if request.GET.get('selected_region_all'):
        selected_region_all = request.GET.get('selected_region_all')
        detail_list = detail_list.filter(region__pk=selected_region_all)
        selected_province_pk = selected_region_all
        selected_city_pk = []
    else:
        selected_city_pk = eval(request.GET.get('selected_city_pk') or '[]')
        if len(selected_city_pk) == 0:
            selected_province_pk = 0
        else:
            node = Q(subregion__pk=selected_city_pk[0])
            for pk in selected_city_pk:
                node |= Q(subregion__pk=pk)
            detail_list = detail_list.filter(node).distinct()
            selected_province_pk = Tag_Subregion.objects.get(pk=selected_city_pk[0]).region.pk
        selected_region_all = None

    ctx = {
        'detail_list': detail_list.order_by('-created_at'),
        'province_list': Tag_Region.objects.all(),
        'region_list': Tag_Subregion.objects.all(),
        'project_list': Tag_Project.objects.all(),
        'language_list': Tag_Language.objects.all(),
        'selected_province_pk': selected_province_pk,
        'selected_city_pk': selected_city_pk,
        'selected_region_all': selected_region_all,
        'selected_language_pk': selected_language_pk,
        'selected_project_pk': selected_project_pk,
    }

    return render(request, 'detail_list.html', ctx)

def alphabet(request):
    print('done')
    selected_detail_list = request.POST.getlist('selected_detail_list[]')
    print(selected_detail_list)
    unordered_detail_list = Planning.objects.none()

    for detail_pk in selected_detail_list:
        unordered_detail_list |= Planning.objects.filter(pk=detail_pk)
    detail_list = unordered_detail_list.order_by('title')

    return render(request, 'detail_list.html', {'detail_list': detail_list})

def recent(request):
    selected_detail_list = request.POST.getlist('selected_detail_list[]')
    unordered_detail_list = Planning.objects.none()

    for detail_pk in selected_detail_list:
        unordered_detail_list |= Planning.objects.filter(pk=detail_pk)
    detail_list = unordered_detail_list.order_by('-created_at')

    return render(request, 'detail_list.html', {'detail_list': detail_list})

# 게시글 화면
def detail(request, pk):
    detail = get_object_or_404(Planning, pk=pk)
    comment_list = Comment.objects.filter(detail__pk=detail.pk).order_by('-created_at')
    comment_form = CommentForm(request.POST or None)
    ctx = {
        'detail': detail,
        'did_like_article': detail.profile_set.filter(pk=request.user.pk).exists(),
        'comment_list': comment_list,
        'comment_form': comment_form,
    }
    return render (request, 'detail.html', ctx)

@login_required
def create(request):
    form = PlanningCreateForm(request.user.profile, request.POST or None, request.FILES or None,)
    if request.method == "POST" and form.is_valid():
        article = form.save(commit=False)
        article.author = request.user
        article.save()
        form.save_m2m()
        return redirect(reverse('planningboard:detail', kwargs={'pk': article.pk}))
    ctx = {
        'form': form,
        'region':Tag_Region.objects.all(),
        'subregion':Tag_Subregion.objects.all()
    }
    return render (request, 'create.html', ctx)

def delete(request, pk):
    if request.method == "POST":
        detail = get_object_or_404(Planning, pk=pk)
        detail.delete()
        return redirect(reverse('planningboard:list'))
    else:
        return HttpResponse(status=400)

def update(request, pk):
    detail = get_object_or_404(Planning, pk=pk)
    form = PlanningCreateForm(request.user.profile, request.POST or None, instance=detail)
    if request.method == "POST" and form.is_valid():
       new_form = form.save(commit=False)
       # new_form.author = request.user
       # new_form.tag_set = request.user.tag_set.all()
       new_form.save()
       form.save_m2m()
       return redirect(reverse('planningboard:detail', kwargs={'pk': pk}))
    ctx = {
        'form': form
    }
    return render(request, 'create.html', ctx)

def get_subregion(request, pk):
    if request.method == "POST":
        subregion_list = Tag_Region.objects.get(pk=pk).get_sebregion()
        return render(request, 'subregion.html',{'subregion_list': subregion_list})

def like(request, pk):
    if request.method == "POST":
        detail = get_object_or_404(Planning, pk=pk)

        if request.user.profile.like_planning.filter(pk=pk).exists():
            detail.profile_set.remove(request.user.profile)
        else:
            detail.profile_set.add(request.user.profile)
        ctx = {
            'did_like_article': detail.profile_set.filter(pk=request.user.pk).exists(),
            'detail': detail,
        }
        return render(request, 'like_button.html', ctx)
    else:
        return HttpResponse(status=400)

def comment_create(request, detail_pk):

    if request.method == "POST":
        detail = Planning.objects.get(pk=detail_pk)
        comment_form = CommentForm(request.POST)

        if comment_form.is_valid():
            new_comment = comment_form.save(commit=False)
            new_comment.author = request.user
            new_comment.detail = detail
            new_comment.save()
            return render(request, 'comment_list.html', {'comment': new_comment})
    return HttpResponse(status=405)


import web
import re
import logging

urls = ('/', 'index',
        '/r/?', 'run',)

render = web.template.render('templates/', globals={'re':re})
app = web.application(urls, globals())
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

db = web.database(dbn='mysql', db='test', user='root', pw='')
logger.info(db)

result = db.query("SELECT datetime", _test=True)
logger.info(result)

class index:
    def GET(self):
        args = web.input(s='')
        print(args)
        return render.index(args.s)

class run:
    def GET(self):
        args2 = web.input(r='')
        print(args2)
        return render.run(args2.r)

if __name__ == '__main__':
    app.run()
    logger.info('Init')


from flask import Blueprint

# blueprint for junior Math
from app.math.junior import math_junior_views

math_junior = Blueprint('math_junior', __name__)

math_junior.before_request(math_junior_views.before_request)
math_junior.add_url_rule('/', 'index', view_func=math_junior_views.index,
                         methods=['GET', 'POST'])

# blueprint for senior Math
from app.math.senior import math_senior_views

math_senior = Blueprint('math_senior', __name__)

math_senior.before_request(math_senior_views.before_request)
math_senior.add_url_rule('/', 'index', view_func=math_senior_views.index,
                  methods=['GET', 'POST'])


"""
https://adventofcode.com/2022/day/19
"""

# NOTE: This solution here is really slow.
# It highly likely that you could use a solver for this like sympy or z3 to
# make this considerably faster. I'm not very familiar with this approach though
# so I'll save it for a learning exercise in future.

import copy
from utils.data import read_data, extract_ints

USE_TEST_DATA = False
SPLIT_BY_LINE = True
data = read_data(USE_TEST_DATA, SPLIT_BY_LINE)

def get_max_geodes(robot_build_costs, time):
    """
    Determine the maximum number of geodes that we can mine given the
    robot_build_costs and time supplied
    """

    initial_state = {
        "ore_robots": 1,
        "clay_robots": 0,
        "obsidian_robots": 0,
        "geode_robots": 0,
        "ore": 0,
        "clay": 0,
        "obsidian": 0,
        "geodes": 0,
        "time": time}
    max_geodes = 0

    state_queue = []
    state_queue.append(initial_state)
    processed = set()

    # Determine the maximum ore, clay and obsidian needed in any single step
    # Once we have enough robots to produce that much ore, clay and obsidian
    # per step then there's no need to build any more as it won't help us any
    # further
    max_ore_needed_per_step = max([robot["ore"] for robot in robot_build_costs.values()])
    max_clay_needed_per_step = robot_build_costs["obsidian"]["clay"]
    max_obsidian_needed_per_step = robot_build_costs["geode"]["obsidian"]

    # Pop states to process from the queue until there aren't any left and
    # we've exhausted all permutations
    while len(state_queue) > 0:
        state = state_queue.pop()

        # If we've seen this exact state before then we don't need to process
        # it again as we'd just be pushing new states that we've already seen
        # on the queue again
        if tuple(state.values()) in processed:
            continue
        else:
            processed.add(tuple(state.values()))

        # Has this state produced a new max number of geodes?
        max_geodes = max(max_geodes, state["geodes"])

        # We're out of time so no point creating new states to process
        time = state["time"]
        if time <= 0:
            continue

        # Need to do some pruning - there are way too many permutations here
        # for just naively brute forcing the whole thing.
        # Let's try throwing away states that seem unlikely to beat the current
        # max_geodes.
        # The maximum number of geodes that any state can generate is
        # (num geode robots * time) + (time*(time - 1) // 2)
        max_geodes_mineable = (state["geode_robots"] * time) + (time*(time - 1)//2)
        if state["geodes"] + max_geodes_mineable <= max_geodes:
            continue

        # Step the current state forward and accumulate mined ore, clay, etc..
        new_state = copy.deepcopy(state)
        new_state["ore"] += new_state["ore_robots"]
        new_state["clay"] += new_state["clay_robots"]
        new_state["obsidian"] += new_state["obsidian_robots"]
        new_state["geodes"] += new_state["geode_robots"]
        new_state["time"] -= 1

        # Create all of the permutations we can from here:
        # 1 - do nothing
        # 2 - build an ore robot
        # 3 - build a clay robot
        # 4 - build an obsidian robot
        # 5 - build a geode robot

        # 1 - don't do anything
        if tuple(new_state.values()) not in processed:
            state_queue.append(new_state)

        # 2 - build an ore robot if we can and it's worth it
        total_ore_from_current_robots = new_state["time"] * new_state["ore_robots"]
        total_ore_that_could_be_needed = max_ore_needed_per_step * new_state["time"]
        if total_ore_from_current_robots < total_ore_that_could_be_needed:
            if (robot_build_costs["ore"]["ore"] <= state["ore"] and
                new_state["ore_robots"] < max_ore_needed_per_step):

                new_state2 = copy.deepcopy(new_state)
                new_state2["ore"] -= robot_build_costs["ore"]["ore"]
                new_state2["ore_robots"] += 1
                state_queue.append(new_state2)

        # 2 - build a clay robot if we can and it's worth it
        total_clay_from_current_robots = new_state["time"] * new_state["clay_robots"]
        total_clay_that_could_be_needed = max_clay_needed_per_step * new_state["time"]
        if total_clay_from_current_robots < total_clay_that_could_be_needed:
            if (robot_build_costs["clay"]["ore"] <= state["ore"]
                and new_state["clay_robots"] < max_clay_needed_per_step):

                new_state2 = copy.deepcopy(new_state)
                new_state2["ore"] -= robot_build_costs["clay"]["ore"]
                new_state2["clay_robots"] += 1
                state_queue.append(new_state2)

        # 3 - build an obsidian robot if we can and it's worth it
        total_obsidian_from_current_robots = new_state["time"] * new_state["obsidian_robots"]
        total_obsidian_that_could_be_needed = max_obsidian_needed_per_step * new_state["time"]
        if total_obsidian_from_current_robots < total_obsidian_that_could_be_needed:
            if (robot_build_costs["obsidian"]["ore"] <= state["ore"] and
                robot_build_costs["obsidian"]["clay"] <= state["clay"] and
                new_state["obsidian_robots"] < max_obsidian_needed_per_step):

                new_state2 = copy.deepcopy(new_state)
                new_state2["ore"] -= robot_build_costs["obsidian"]["ore"]
                new_state2["clay"] -= robot_build_costs["obsidian"]["clay"]
                new_state2["obsidian_robots"] += 1
                state_queue.append(new_state2)

        # 4 - build a geode robot if we can
        if (robot_build_costs["geode"]["ore"] <= state["ore"] and
            robot_build_costs["geode"]["obsidian"] <= state["obsidian"]):

            new_state2 = copy.deepcopy(new_state)
            new_state2["ore"] -= robot_build_costs["geode"]["ore"]
            new_state2["obsidian"] -= robot_build_costs["geode"]["obsidian"]
            new_state2["geode_robots"] += 1
            state_queue.append(new_state2)

    return max_geodes

total_quality = 0
maximum_geodes = 1
for i, line in enumerate(data):

    # Read the blueprint data in
    ints = extract_ints(line)
    blueprint_id = ints[0]
    ore_robot_cost = {"ore": ints[1]}
    clay_robot_cost = {"ore": ints[2]}
    obsidian_robot_cost = {"ore": ints[3], "clay": ints[4]}
    geode_robot_cost = {"ore": ints[5], "obsidian": ints[6]}

    robot_costs = {"ore": ore_robot_cost,
                   "clay": clay_robot_cost,
                   "obsidian": obsidian_robot_cost,
                   "geode": geode_robot_cost}

    # Part 1
    # Sum the max geodes * blueprint id for each blueprint
    total_quality += blueprint_id * get_max_geodes(robot_costs, 24)

    # Part 2
    # For the first 3 blueprints only calculate the product of max geodes from
    # all 3 blueprints
    if i < 3:
        maximum_geodes *= get_max_geodes(robot_costs, 32)

print(total_quality)
print(maximum_geodes)


"""
compare computing times and make sure output is identical
"""

from cltoolkit import Wordlist
from pacs.colexifications import (
        affix_colexifications,
        affix_colexifications_by_pairwise_comparison
        )
from pycldf import Dataset
from pyclts import CLTS
from lexibank_allenbai import Dataset as AllenBai

import timeit
from tabulate import tabulate
import functools

wl = Wordlist([Dataset.from_metadata(AllenBai().cldf_dir.joinpath("cldf-metadata.json"))],
              ts=CLTS().bipa)

digraphA = affix_colexifications(wl)
digraphB = affix_colexifications_by_pairwise_comparison(wl)

diffs = []
for nA, nB, data in digraphA.edges(data=True):
    countA = data["count"]
    try:
        countB = digraphB[nA][nB]["count"]
        source_a, target_a = " / ".join(data["source_forms"]), " / ".join(data["target_forms"])
        source_b, target_b = " / ".join(digraphB[nA][nB]["source_forms"]), " / ".join(digraphB[nA][nB]["target_forms"])
    except:
        countB = 0
        source_a, target_a = " / ".join(data["source_forms"]), " / ".join(data["target_forms"])
        source_b, target_b = "", ""
    if countA != countB:
        diffs += [(nA, nB, countA, countB, source_a, target_a, source_b, target_b)]

if diffs:
    print(tabulate(diffs))
else:
    print("[i] no differences found for the fast and the slow computation of partial colexifications")

def colex(fun):
    fun(wl)

t_a = timeit.Timer(functools.partial(colex, affix_colexifications))
t_b = timeit.Timer(functools.partial(colex, affix_colexifications_by_pairwise_comparison))

t1 = t_a.timeit(10)
t2 = t_b.timeit(10)
print("Affix Colexification Fast: {0:.2f}".format(t1))
print("Affix Colexification Slow: {0:.2f}".format(t2))


import os.path
import argparse
from tkinter import filedialog
from duplicates import Duplicates
from cropBorder import CropBorder

parser = argparse.ArgumentParser(description='Provides several tools to manage folders of images')

availablePrograms = ['dupe', 'exactdupe', 'cropborder']
availableProgramsStr = ', '.join(availablePrograms)
parser.add_argument('program', type=str,
                    help=f'Specific tool you want to run (Default: dupe)\nAvailable programs: {availableProgramsStr}',
                    default='dupe')

parser.add_argument('path', type=str,
                    help='Path of an image folder to test (Default: . / the path you are currently in)',
                    default='.')

args = parser.parse_args()
if not os.path.exists(args.path):
    path = filedialog.askdirectory()
    if os.path.exists(path):
        raise Exception('Please specify a valid path')
else:
    path = args.path

if not args.program.lower() in availablePrograms:
    raise Exception(f'Please specify a correct program ({availableProgramsStr})')

#print(args)
parser.print_help()

if args.program.lower() == 'dupe':
    #images = Duplicates(path)
    pass
elif args.program.lower() == 'cropborder':
    CropBorder(path)


#
# File: AWESEM_Testbench_Main.py
# ------------------------------
# Authors: Brion Ye, Erick Blankenberg
# Date: 9/28/2018
#
# Description:
#   This class starts the application and ties
#   everything together. This also sets up and contains
#   functions that handle GUI requests. See the docs folder
#   for an overview of the system.
#
#   General:
#      PyQt tutorial:       http://pythonforengineers.com/your-first-gui-app-with-python-and-pyqt/
#      Redirecting prints:  https://stackoverflow.com/questions/44432276/print-out-python-console-output-to-qtextedit
#      Multithreading pref:
#   Image processing:
#      Simple Itk:          http://www.simpleitk.org
#      Simple elastix:      https://simpleelastix.github.io
#
#   Notes:
#       - Attempted to use mpipe but worked inconsistently on my windows laptop
#
#   Moving Forward:
#       - Apparently one thread is faster than two for data acquisition and handling, we
#         should look into multiprocessing, even the raspberry pi has more than one core.
#       - Work on and integrate analysis module. Use simpleitk
#       - Find a framework to make preformance timing easier
#
#   TODO:
#       - Only 5 of the 6 default settings for driving waveform trigger a callback when setting defaults, should check everything else as well
#       - Seems like the scrolling message box does not update until a user interacts with it,
#         the whole thing might be a waste of time anyway. I was trying to make seeing errors easier
#         for non-developer users.
#       - Seems like there might be rollover or some sort of timing issue for the latter half of the rising part
#         of some waveforms. They temporarily become very choppy.

doChangeCPU = False # Set to true to set CPU affinity manually

import scipy
import ast
import psutil
import os
import glob
import sys
import numpy
import datetime
from   scipy                         import signal
from   matplotlib                    import cm
from   matplotlib                    import pyplot as plt
from   matplotlib.backends.backend_agg import FigureCanvasAgg
from   qimage2ndarray                import byte_view
from   qimage2ndarray                import array2qimage
from   PyQt5.QtCore                  import *
from   PyQt5.QtGui                   import *
from   PyQt5.QtWidgets               import *
from   PyQt5                         import uic
from   AWESEM_PiPion_Interface       import AWESEM_PiPion_Interface
import AWESEM_Constants              as Const
import AWESEM_Register               as Register
import AWESEM_Analysis               as Analysis

if hasattr(Qt, 'AA_EnableHighDpiScaling'):
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
if hasattr(Qt, 'AA_UseHighDpiPixmaps'):
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)

Ui_MainWindow, QtBaseClass = uic.loadUiType("TestBench.ui")

# Used for redirecting console output
class Stream(QObject):
    newText    = pyqtSignal(str)
    __allowAll   = True
    __allowedSet = set()

    def allowAll(self, doAllow):
        self.__allowAll = doAllow

    def includePrefix(self, token, doInclude):
        if(doInclude):
            if(token not in self.__allowedSet):
                self.__allowedSet.add(token)
                return True
            return False
        else:
            if(token in self.__allowedSet):
                self.__allowedSet.remove(token)
                return True
            return False

    def write(self, text):
        if not self.__allowAll:
            for token in self.__allowedSet:
                if(text.startswith(token)):
                    self.newText.emit(str(text))
                    return
        else:
            self.newText.emit(str(text))

# The main program
class TestBench(QMainWindow):

    # Internal components
    __MCUInterface      = None
    __registerTh        = None
    __consoleOut        = None
    __ScanImage         = QImage(Const.PATH_BACKGROUNDIMAGE).scaled(Const.RES_W, Const.RES_H)
    __ColorMap          = cm.get_cmap('viridis')
    __UiElems           = None
    __WaveTables        = None # Dictionary of {"Name": tableArray}
    __SystemModels      = None # Dictionary of {"Name": signalContinousLTIModel}

    # Trackers for GUI elements, used so that the menu will bounce back to previous menu item after selecting option to load custom data
    __currentXWaveformOpt  = None
    __currentYWaveformOpt  = None

    __currentLUTModeOpt    = None
    # Tracker for current scale so that files can have these when exported, set to None to disable axis
    __currentXLength        = None # (microns)
    __currentYLength        = None # (microns)

    def __init__(self, *args, **kwargs):
        super(TestBench, self).__init__(*args, **kwargs)
        # Structure is MCU -(interface)-> dataTh -(double buffer)-> registerTh -(callback)-> monitor
        self.__UiElems = Ui_MainWindow()
        self.__UiElems.setupUi(self)
        self.__MCUInterface = AWESEM_PiPion_Interface()
        self.__registerTh   = Register.Register(self.updateQTImage, self.__MCUInterface)
        self.setUiProperties()
        self.loadDefaultWaveTables()
        self.loadDefaultModels()
        self.setDefaults()
        self.linkUiCallbacks()
        self.__registerTh.start()

        self.updateWaveforms()

    def __del__(self):
        self.shutdownResources()

    def shutdownResources(self):
        print("Closing Resources")
        self.__MCUInterface.close()

        self.__registerTh.close()
        print("Resources Closed")
        sys.stdout = sys.__stdout__ # Sets print mode back to normal

    def loadModel(self, filePath):
        _, systemFullName = os.path.split(filePath) # Returns path and file name
        systemName, _ = os.path.splitext(systemFullName) # Returns extension and name
        if self.__UiElems.Sampling_LUT_Combobox.findText(systemName) == -1:
            try:
                with open(filePath,'r') as inf:
                    systemParameters = ast.literal_eval(inf.read())
                self.__SystemModels[systemName] = signal.ZerosPolesGain(systemParameters['Zeros'], systemParameters['Poles'], systemParameters['Gain'])
                self.__UiElems.Sampling_LUT_Combobox.blockSignals(True)
                self.__UiElems.Sampling_LUT_Combobox.insertItem(0, systemName)
                self.__UiElems.Sampling_LUT_Combobox.blockSignals(False)
            except Exception as e:
                print("Error: Failed to load model \"%s\" with error \"%s\"" % (filePath, e))
        else:
            print("Error: Stopped attempted load of redundant model, existing model at: %s" % (self.__UiElems.Sampling_LUT_Combobox.findText(waveName)))

    def loadDefaultModels(self):
        # Adds default options
        self.__UiElems.Sampling_LUT_Combobox.blockSignals(True)
        self.__UiElems.Sampling_LUT_Combobox.insertItem(0, "Import Model")
        self.__UiElems.Sampling_LUT_Combobox.insertItem(0, "Linear")
        self.__UiElems.Sampling_LUT_Combobox.insertItem(0, "Axis Waveform")
        self.__UiElems.Sampling_LUT_Combobox.blockSignals(False)
        # Loads in files
        self.__SystemModels = dict()
        availableModels = glob.glob(os.path.join(os.path.abspath(''), "SysModels", "*.txt"))
        for filePath in availableModels:
            self.loadModel(filePath)
        self.__UiElems.Sampling_LUT_Combobox.setCurrentIndex(0)

    # Loads wave table at the given directory into available waveform dictionary
    def loadWaveform(self, filePath):
        _, waveFullName = os.path.split(filePath) # Returns path and file name
        waveName, _ = os.path.splitext(waveFullName) # Returns extension and name
        if self.__UiElems.Vertical_Waveform_Combobox.findText(waveName) == -1:
            try:
                dataVals = numpy.genfromtxt(filePath)
                if(dataVals.shape[0] != 256):
                    print("Error: Failed to load waveform \"%s\", length of %s not 256" % (filePath, dataVals.shape[0]))
                elif(numpy.any(numpy.logical_or(dataVals > 32767, dataVals < -32767))):
                    print("Error: Failed to load waveform \"%s\", some values larger than 16 bit limit" % (filePath))
                else:
                    self.__WaveTables[waveName] = dataVals
                    self.__UiElems.Vertical_Waveform_Combobox.blockSignals(True)
                    self.__UiElems.Horizontal_Waveform_Combobox.blockSignals(True)
                    self.__UiElems.Horizontal_Waveform_Combobox.insertItem(0, waveName)
                    self.__UiElems.Vertical_Waveform_Combobox.insertItem(0, waveName)
                    self.__UiElems.Vertical_Waveform_Combobox.blockSignals(False)
                    self.__UiElems.Horizontal_Waveform_Combobox.blockSignals(False)
            except Exception as e:
                print("Error: Failed to load waveform \"%s\" with error \"%s\"" % (filePath, e))
        else:
            print("Error: Stopped attempted load of redundant waveform, existing waveform at: %s" % (self.__UiElems.Vertical_Waveform_Combobox.findText(waveName)))

    # Loads any waveform tables already in the "Waveforms" directory
    def loadDefaultWaveTables(self):
        # Add default options
        self.__UiElems.Vertical_Waveform_Combobox.blockSignals(True)
        self.__UiElems.Horizontal_Waveform_Combobox.blockSignals(True)
        self.__UiElems.Horizontal_Waveform_Combobox.insertItem(0, "Load Custom")
        self.__UiElems.Vertical_Waveform_Combobox.insertItem(0, "Load Custom")
        self.__UiElems.Vertical_Waveform_Combobox.blockSignals(False)
        self.__UiElems.Horizontal_Waveform_Combobox.blockSignals(False)
        # Load files from other directories
        self.__WaveTables = dict()
        availableTables = glob.glob(os.path.join(os.path.abspath(''), "Waveforms", "*.csv"))
        for filePath in availableTables:
            self.loadWaveform(filePath)
        self.__UiElems.Vertical_Waveform_Combobox.setCurrentIndex(0)
        self.__UiElems.Horizontal_Waveform_Combobox.setCurrentIndex(0)

    def setUiProperties(self):
        self.__UiElems.Vertical_Waveform_Combobox.setInsertPolicy(QComboBox.InsertAtTop)
        self.__UiElems.Horizontal_Waveform_Combobox.setInsertPolicy(QComboBox.InsertAtTop)
        self.__UiElems.Sampling_LUT_Combobox.setInsertPolicy(QComboBox.InsertAtTop)

    #
    # Description:
    #   Links functions to button presses
    #
    def linkUiCallbacks(self):
        # Scan Controls
        self.__UiElems.Scan_Pushbutton.clicked.connect(self.toggleScanning)
        self.__UiElems.Save_Pushbutton.clicked.connect(self.saveImage)
        self.__UiElems.Clear_Pushbutton.clicked.connect(self.clearScreen)

        # Vertical Axis
        self.__UiElems.Vertical_Waveform_Combobox.currentTextChanged.connect(self.updateWaveforms)
        self.__UiElems.Vertical_Amplitude_Spinbox.valueChanged.connect(self.updateWaveforms)
        self.__UiElems.Vertical_Frequency_Spinbox.valueChanged.connect(self.updateWaveforms)

        # Horizontal Axis
        self.__UiElems.Horizontal_Waveform_Combobox.currentTextChanged.connect(self.updateWaveforms)
        self.__UiElems.Horizontal_Amplitude_Spinbox.valueChanged.connect(self.updateWaveforms)
        self.__UiElems.Horizontal_Frequency_Spinbox.valueChanged.connect(self.updateWaveforms)

        # Sampling
        self.__UiElems.Sampling_Frequency_Spinbox.valueChanged.connect(self.updateSamplingFrequency)
        # self.__UiElems.Sampling_Averages_Spinbox.valueChanged.connect(self.setSamplingAverages)
        self.__UiElems.Sampling_Phase_Vertical_Spinbox.valueChanged.connect(self.updateSamplingReconstruction)
        self.__UiElems.Sampling_Phase_Horizontal_Spinbox.valueChanged.connect(self.updateSamplingReconstruction)
        self.__UiElems.Sampling_LUT_Combobox.currentIndexChanged.connect(self.updateSamplingReconstruction)
        self.__UiElems.Sampling_Horizontal_Collection_Combobox.currentIndexChanged.connect(self.updateSamplingReconstruction)
        self.__UiElems.Sampling_Vertical_Collection_Combobox.currentIndexChanged.connect(self.updateSamplingReconstruction)

        # Window
        self.__UiElems.Plotter_Label.setPixmap(QPixmap.fromImage(self.__ScanImage).scaled(self.__UiElems.Plotter_Label.width(), self.__UiElems.Plotter_Label.height()))

        # Console output
        #sys.stdout = self.__consoleOut # COMBAK:
        self.__UiElems.Console_Preformance_Checkbox.stateChanged.connect(self.setConsolePreferences)
        self.__UiElems.Console_Verbose_Checkbox.stateChanged.connect(self.setConsolePreferences)
        self.__consoleOut    = Stream(newText = self.onUpdateText)

    def setDefaults(self):
        # Remember that when the combobox values are changed, this triggers the
        # callbacks and so the MCU is updated etc.

        # Scan Controls

        # Vertical Axis

        defaultVerticalWaveformIndex = self.__UiElems.Vertical_Waveform_Combobox.findText(Const.DEFAULT_VERTWAV)
        if(defaultVerticalWaveformIndex >= 0):
            self.__UiElems.Vertical_Waveform_Combobox.setCurrentIndex(defaultVerticalWaveformIndex)
        else:
            print("Default set vertical waveform does not match a loaded file, check the value in the constants file.")
        self.__UiElems.Vertical_Amplitude_Spinbox.setValue(Const.DEFAULT_VERTAM)
        self.__UiElems.Vertical_Frequency_Spinbox.setValue(Const.DEFAULT_VERTHZ)
        self.__UiElems.Sampling_Phase_Vertical_Spinbox.setValue(Const.DEFAULT_VERTPHASE)

        # Horizontal Axis
        defaultHorizontalWaveformIndex = self.__UiElems.Horizontal_Waveform_Combobox.findText(Const.DEFAULT_HORZWAV)
        if(defaultHorizontalWaveformIndex >= 0):
            self.__UiElems.Horizontal_Waveform_Combobox.setCurrentIndex(defaultHorizontalWaveformIndex)
        else:
            print("Default set horizontal waveform does not match a loaded file, check the value in the constants file.")
        self.__UiElems.Horizontal_Amplitude_Spinbox.setValue(Const.DEFAULT_HORZAM)
        self.__UiElems.Horizontal_Frequency_Spinbox.setValue(Const.DEFAULT_HORZHZ)
        self.__UiElems.Sampling_Phase_Horizontal_Spinbox.setValue(Const.DEFAULT_HORZPHASE)

        # Sampling and reconstruction
        defaultReconstructionIndex = self.__UiElems.Sampling_LUT_Combobox.findText(Const.DEFAULT_LUTMODEL)
        if(defaultReconstructionIndex >= 0):
            self.__UiElems.Sampling_LUT_Combobox.setCurrentIndex(defaultReconstructionIndex)
        else:
            print("Default set LUT method does not match a loaded model or method, check the value in the constants file.")
        self.__UiElems.Sampling_Frequency_Spinbox.setValue(Const.DEFAULT_ADC_SAMPLEFREQUENCY)
        self.__UiElems.Sampling_Horizontal_Collection_Combobox.setCurrentIndex(Const.DEFAULT_HORZSRC)
        self.__UiElems.Sampling_Vertical_Collection_Combobox.setCurrentIndex(Const.DEFAULT_VERTSRC)
        # TODO sample averaging

        # Console
        self.__UiElems.Console_Preformance_Checkbox.setCheckState(1)
        self.__UiElems.Console_Verbose_Checkbox.setCheckState(1)

    #
    # Description:
    #   Function called as emission by stdo        self.linkUiCallbacks()ut replacement. Prints
    #   the console contents to the monitor. TODO Current monitor
    #   is too small and the user is allowed to edit the contents manually
    #   also the monitor should be self-clearing
    #
    def onUpdateText(self, text):
        cursor = self.__UiElems.Console_Output_TextBox.textCursor()
        cursor.movePosition(QTextCursor.End)
        cursor.insertText(text)
        self.__UiElems.Console_Output_TextBox.setTextCursor(cursor)
        self.__UiElems.Console_Output_TextBox.ensureCursorVisible()

    # Description:
    #   Updates the reference qt object from the given mapped data
    #
    # Parameters:
    #   'valueVectors'  Numpy array of mapped image intensities of the format [xPosition, yPosition, intensityValue];[...]...
    #
    def updateQTImage(self, valueVectors):
        if valueVectors is not None:
            valueVectorColors = (self.__ColorMap(valueVectors[:, 2] / 255.0) * 255.0).astype(numpy.uint8) # Format is RGBA
            interiorView = byte_view(self.__ScanImage)
            valueVectors[:, 1] = -1 * (valueVectors[:, 1] - (Const.RES_H - 1)) # Flips coordinates so increasing goes from bottom up
            interiorView[valueVectors[:, 1], valueVectors[:, 0], 0] = valueVectorColors[:, 2] # Converts rgba to bgra
            interiorView[valueVectors[:, 1], valueVectors[:, 0], 1] = valueVectorColors[:, 1]
            interiorView[valueVectors[:, 1], valueVectors[:, 0], 2] = valueVectorColors[:, 0]
            interiorView[valueVectors[:, 1], valueVectors[:, 0], 3] = valueVectorColors[:, 3]
            self.__UiElems.Plotter_Label.setPixmap(QPixmap.fromImage(self.__ScanImage).scaled(self.__UiElems.Plotter_Label.width(), self.__UiElems.Plotter_Label.height()))

    #
    # Description:
    #   Starts and stops data acquisiton and waveform generation. Updates button
    #   text as well.
    #
    def toggleScanning(self):
        if(self.__MCUInterface.isScanning()):
            self.__UiElems.Scan_Pushbutton.setText("Start Scanning")
            self.__MCUInterface.pauseEvents()
            self.__registerTh.halt()
        else:
            self.__UiElems.Scan_Pushbutton.setText("Stop Scanning")
            self.__MCUInterface.beginEvents()
            self.__registerTh.commence()

    #
    # Description:
    #   Saves image currenly on display to memory under the "Captures"
    #   folder.
    #
    def saveImage(self):
        settingsString = "[%s, %s]-[%0.2f,%0.2f](Hz)-[%0.2f,%0.2f](Vpp)-[%s, %s](Microns)" % (self.__UiElems.Horizontal_Waveform_Combobox.currentText(), self.__UiElems.Vertical_Waveform_Combobox.currentText(), self.__UiElems.Horizontal_Frequency_Spinbox.value(), self.__UiElems.Vertical_Frequency_Spinbox.value(), self.__UiElems.Horizontal_Amplitude_Spinbox.value(), self.__UiElems.Vertical_Amplitude_Spinbox.value(), self.__currentXLength, self.__currentYLength)
        totalString    = "Capture_%s_%s.bmp" % (datetime.datetime.now().strftime("%Y-%m-%d[%H-%M-%S]"), settingsString)
        if not self.__ScanImage.save(os.path.join(os.getcwd(), 'Captures', totalString), format = "BMP"):
            print("Failed to Save Image")

    #
    # Description:
    #   Clears image currently on monitor.
    #
    def clearScreen(self):
        self.__ScanImage = QImage(Const.PATH_BACKGROUNDIMAGE).scaled(Const.RES_W, Const.RES_H)
        self.__UiElems.Plotter_Label.setPixmap(QPixmap.fromImage(self.__ScanImage).scaled(self.__UiElems.Plotter_Label.width(), self.__UiElems.Plotter_Label.height()))

    #
    # Description:
    #   Does a frequency survey to approximate the impulse response of the system.
    #   See phase demo for details.
    #
    def calibrateLTIModel(self):
        print("LTI Calibration not fully implemented")

    #
    # Description:
    #   Builds a displacement map to capture non-linear displacement distortion
    #   not accounted for by the LTI model.
    #
    def calibrateDisplacementGrid(self):
        print("Displacement Grid Calibration not fully implemented.")

    #
    # Descripion:
    #   Used to set filters for strings printed to the console.
    #
    def setConsolePreferences(self):
        self.__consoleOut.includePrefix("Error", True)
        self.__consoleOut.includePrefix("Pref", self.__UiElems.Console_Preformance_Checkbox.isChecked())
        self.__consoleOut.allowAll(self.__UiElems.Console_Verbose_Checkbox.isChecked())

    #
    # Description:
    #   Updates the output waveforms to whatever is currently listed in
    #   the GUI.
    #
    def updateWaveforms(self):
        # Handles vertical
        verticalLabel = self.__UiElems.Vertical_Waveform_Combobox.currentText()
        if verticalLabel in self.__WaveTables: # Is an existing waveform
            self.__MCUInterface.setDacMagnitude(1, self.__UiElems.Vertical_Amplitude_Spinbox.value())
            self.__MCUInterface.setDacFrequency(1, self.__UiElems.Vertical_Frequency_Spinbox.value())
            self.__MCUInterface.setCustomWaveformData(1, self.__WaveTables[verticalLabel])
            self.__MCUInterface.setDacWaveform(1, 4)
            self.__currentYWaveformOpt = self.__UiElems.Vertical_Waveform_Combobox.currentIndex()
        elif verticalLabel == "Load Custom": # Loads new waveform
            directories =  QFileDialog.getOpenFileNames(caption = "Select one or more waveform files.", directory = os.path.join(os.path.abspath(''), "Waveforms"));
            for currentDirectoryIndex in range(len(directories) - 1): # Last item is a status code
                currentDirectory = directories[currentDirectoryIndex]
                if(len(currentDirectory) > 0): # See above
                    self.loadWaveform(currentDirectory[0])
            self.__UiElems.Vertical_Waveform_Combobox.setCurrentIndex(self.__currentYWaveformOpt)
            return
        else:
            print("Error: Invalid Waveform Choice for Vertical")
            return

        # Handles vertical
        horizontalLabel = self.__UiElems.Horizontal_Waveform_Combobox.currentText()
        if horizontalLabel in self.__WaveTables: # Is an existing waveform
            self.__MCUInterface.setDacMagnitude(0, self.__UiElems.Horizontal_Amplitude_Spinbox.value())
            self.__MCUInterface.setDacFrequency(0, self.__UiElems.Horizontal_Frequency_Spinbox.value())
            self.__MCUInterface.setCustomWaveformData(0, self.__WaveTables[horizontalLabel])
            self.__MCUInterface.setDacWaveform(0, 4)
            self.__currentXWaveformOpt = self.__UiElems.Horizontal_Waveform_Combobox.currentIndex()
        elif horizontalLabel == "Load Custom": # Loads new waveform
            directories =  QFileDialog.getOpenFileNames(caption = "Select one or more waveform files.", directory = os.path.join(os.path.abspath(''), "Waveforms"));
            for currentDirectoryIndex in range(len(directories) - 1): # Last item is a status code
                currentDirectory = directories[currentDirectoryIndex]
                if(len(currentDirectory) > 0): # See above
                    self.loadWaveform(currentDirectory[0])
            self.__UiElems.Horizontal_Waveform_Combobox.setCurrentIndex(self.__currentXWaveformOpt)
            return
        else:
            print("Error: Invalid Waveform Choice for Horizontal")
            return

        # Updates
        self.updateSamplingReconstruction()
        # Refreshes MCU
        if(self.__MCUInterface.isScanning()): # If not scanning will take effect on startup anyway
            self.__MCUInterface.pauseEvents()
            self.__MCUInterface.beginEvents()

    #
    # Description
    #   Trims image to smallest bounding box that contains pixels whose alpha
    #   value is greater than 1 (not fully transparent).
    #
    def cropInvisAway(self, image):
        imageInverseMask = numpy.logical_not(numpy.logical_or(image[:, :, 3] == 0, numpy.sum(image[:, :, :], axis = 2) == 255 * 4)) # Areas where plotting occured have high alpha values, others have an alpha value of zero or are white
        imageHorizontalSums = numpy.sum(imageInverseMask, axis = 0)
        nonZeroHoriz = numpy.nonzero(imageHorizontalSums)[0]
        firstHoriz   = nonZeroHoriz[0]
        lastHoriz    = nonZeroHoriz[-1]
        imageVerticalSums = numpy.sum(imageInverseMask, axis = 1)
        nonZeroVert  = numpy.nonzero(imageVerticalSums)[0]
        firstVert    = nonZeroVert[0]
        lastVert     = nonZeroVert[-1]
        croppedImage = image[(firstVert):(lastVert + 2), (firstHoriz):(lastHoriz + 2), :]
        return croppedImage

    #
    # Description
    #   Adds scale labels to edges of the image in microns. If None is passed
    #   for xMaxMicrons or yMaxMicrons than the corresponding axis is set to
    #   appear blank.
    #
    def __updateXAxisLabel(self, xMaxMicrons):
        xAxisImage = numpy.zeros((10, 10, 4)) # Does not really matter, just has to be x, x, 4 b/c it will be scaled
        if xMaxMicrons is not None and xMaxMicrons > 0.0:
            fig, ax = plt.subplots(dpi = 300)
            ax.set_aspect(0.1)
            ax.set_yticks([])
            ax.tick_params(
                axis='y',          # changes apply to the x-axis
                which='both',      # both major and minor ticks are affected
                bottom=False,      # ticks along the bottom edge are off
                top=False,         # ticks along the top edge are off
                labelbottom=False) # labels along the bottom edge are off
            ax.set_xlim(0, xMaxMicrons)
            xTick_objects = ax.xaxis.get_major_ticks()
            xTick_objects[0].label1.set_horizontalalignment('left')   # left align first tick
            xTick_objects[-1].label1.set_horizontalalignment('right') # right align last tick
            plt.xlabel("Microns")
            # > Gets canvas buffer
            canvas = FigureCanvasAgg(fig);
            canvas.draw();
            s, (width, height) = canvas.print_to_buffer()
            xAxisImage = self.cropInvisAway(numpy.frombuffer(s, numpy.uint8).reshape((height, width, 4)))

        self.__UiElems.Horizontal_Scale_Label.setPixmap(QPixmap.fromImage(array2qimage(xAxisImage.copy()).scaled(self.__UiElems.Horizontal_Scale_Label.width(), self.__UiElems.Horizontal_Scale_Label.height())))

    def __updateYAxisLabel(self, yMaxMicrons):
        yAxisImage = numpy.zeros((10, 10, 4)) # As Alpha is zero will appear blank
        if yMaxMicrons is not None and yMaxMicrons > 0.0:
            fig, ax = plt.subplots(dpi = 300)
            ax.set_aspect(1000)
            ax.set_xticks([])
            ax.tick_params(
                axis='x',          # changes apply to the x-axis
                which='both',      # both major and minor ticks are affected
                bottom=False,      # ticks along the bottom edge are off
                top=False,         # ticks along the top edge are off
                labelbottom=False) # labels along the bottom edge are off
            ax.set_ylim(0, yMaxMicrons)
            yTick_objects = ax.yaxis.get_major_ticks()
            yTick_objects[0].label1.set_verticalalignment('bottom')   # left align first tick
            yTick_objects[-1].label1.set_verticalalignment('top') # right align last tick
            plt.ylabel("Microns")
            # > Gets canvas buffer
            canvas = FigureCanvasAgg(fig);
            canvas.draw();
            s, (width, height) = canvas.print_to_buffer()
            yAxisImage = self.cropInvisAway(numpy.frombuffer(s, numpy.uint8).reshape((height, width, 4)))

        self.__UiElems.Vertical_Scale_Label.setPixmap(QPixmap.fromImage(array2qimage(yAxisImage.copy()).scaled(self.__UiElems.Vertical_Scale_Label.width(), self.__UiElems.Vertical_Scale_Label.height())))

    #
    # Description
    #   Returns an executable callback that filters timestamps based on whether
    #   they correspond to times in the displacement LUT where the stage is rising
    #   (after the lowest displacement point and before the highest point)
    #   or falling. Returns None if no filtering should occur based on the filter
    #   text.
    #
    def getTimestampFilterFunc(self, filteringText, frequency, stableLUT, stableTimes):
        period   = (1.0 / frequency)
        filterFunction  = None
        crestTime   = stableTimes[numpy.argmax(stableLUT)]
        troughTime  = stableTimes[numpy.argmin(stableLUT)]
        if filteringText == "Rising":
            def filterFunction(inputTime):
                inputTime = numpy.fmod(inputTime, period)
                if troughTime < crestTime: # rising during midpoint
                    return numpy.logical_and(troughTime < inputTime, inputTime < crestTime)
                # falling during midpoint
                return numpy.logical_or(troughTime < inputTime, inputTime < crestTime)
        elif filteringText == "Falling":
            def filterFunction(inputTime):
                inputTime = numpy.fmod(inputTime, period) # TODO mod makes me sad
                if crestTime < troughTime: # falling during midpoint
                    return numpy.logical_and(crestTime < inputTime, inputTime < troughTime)
                # rising during midpoint
                return numpy.logical_or(crestTime < inputTime, inputTime < troughTime)
        return filterFunction

    #
    # Desription:
    #   Updates the lookup methods and modes used for
    #   interpreting sampled data.
    #
    def updateSamplingReconstruction(self):
        # What we have
        xPhase          = self.__UiElems.Sampling_Phase_Horizontal_Spinbox.value() # Only used for "Axis Waveform" mode currently
        yPhase          = self.__UiElems.Sampling_Phase_Vertical_Spinbox.value()
        xFrequency      = self.__UiElems.Horizontal_Frequency_Spinbox.value() # Spinbox is hz
        yFrequency      = self.__UiElems.Vertical_Frequency_Spinbox.value()   # Spinbox is hz
        xVpp            = self.__UiElems.Horizontal_Amplitude_Spinbox.value()
        yVpp            = self.__UiElems.Vertical_Amplitude_Spinbox.value()
        currentLUTMode  = self.__UiElems.Sampling_LUT_Combobox.currentText()
        # What will be determined
        xFunction       = None
        yFunction       = None
        xTotalLength    = None # Width of image in microns
        yTotalLength    = None # Height of image in microns
        xFilterFunction = None
        yFilterFunction = None
        xTimeOffset     = 0.0
        yTimeOffset     = 0.0

        # Import new LTI model
        if(currentLUTMode == "Import Model"):
            directories =  QFileDialog.getOpenFileNames(caption = "Select one or more system model files.", directory = os.path.join(os.path.abspath(''), "SysModels"))
            for currentDirectoryIndex in range(len(directories) - 1): # Last item is a status code
                currentDirectory = directories[currentDirectoryIndex]
                if(len(currentDirectory) > 0):
                    self.loadModel(currentDirectory[0])
            self.__UiElems.Sampling_LUT_Combobox.setCurrentIndex(self.__currentLUTModeOpt)
            return
        else:
            self.__currentLUTModeOpt = self.__UiElems.Sampling_LUT_Combobox.currentIndex()

        # Selected an existing system model
        if currentLUTMode in self.__SystemModels:
            systemModel = self.__SystemModels[currentLUTMode]
            # X Axis
            xPeriod     = 1.0 / xFrequency
            xWaveText   = self.__UiElems.Horizontal_Waveform_Combobox.currentText()
            xWaveTable  = self.__WaveTables[xWaveText]
            xStableTimes, xStableLUT = Analysis.findSteadyStateResp(systemModel, xWaveTable, xFrequency, xVpp)
            xScreenLUT, xTotalLength = Analysis.normalizeDisplacement(xStableLUT, Const.RES_W)
            xFunction     = lambda times: numpy.interp(x = times, xp = xStableTimes, fp = xScreenLUT, period = xPeriod)
            # Y Axis
            yPeriod     = 1.0 / yFrequency
            yWaveText   = self.__UiElems.Vertical_Waveform_Combobox.currentText()
            yWaveTable  = self.__WaveTables[yWaveText]
            yStableTimes, yStableLUT = Analysis.findSteadyStateResp(systemModel, yWaveTable, yFrequency, yVpp)
            yScreenLUT, yTotalLength = Analysis.normalizeDisplacement(yStableLUT, Const.RES_H)
            yFunction     = lambda times: numpy.interp(x = times, xp = yStableTimes, fp = yScreenLUT, period = yPeriod)

            # Sets filter
            xFilteringText = self.__UiElems.Sampling_Horizontal_Collection_Combobox.currentText()
            xFilterFunction = self.getTimestampFilterFunc(xFilteringText, xFrequency, xScreenLUT, xStableTimes)
            yFilteringText = self.__UiElems.Sampling_Vertical_Collection_Combobox.currentText()
            yFilterFunction = self.getTimestampFilterFunc(yFilteringText, yFrequency, yScreenLUT, yStableTimes)

        # Lays out data into modern art image for testing and calibration
        elif(currentLUTMode == "Linear"):
            xFunction = lambda inputTime : Analysis.sawTooth(inputTime, (Const.RES_W * 0.5) - 1, xFrequency, 0.0)
            yFunction = lambda inputTime : Analysis.sawTooth(inputTime, (Const.RES_H * 0.5) - 1, yFrequency, 0.0)

        # Assumes that system plant is 1 (no distortion) and allows for manual phase adjustment
        elif currentLUTMode == "Axis Waveform":
            # X Axis
            xPeriod       = 1.0 / xFrequency
            xWaveText     = self.__UiElems.Horizontal_Waveform_Combobox.currentText()
            xWaveTable    = self.__WaveTables[xWaveText]
            xStableLUT    = numpy.append(xWaveTable, xWaveTable[0]) # Forces wraparound
            xStableTimes  = numpy.linspace(start = 0, stop = xPeriod, num = xStableLUT.shape[0])
            xScreenLUT, _ = Analysis.normalizeDisplacement(xStableLUT, Const.RES_W)
            xFunction     = lambda times: numpy.interp(x = times, xp = xStableTimes, fp = xScreenLUT, period = xPeriod)
            # Y Axis
            yPeriod       = 1.0 / yFrequency
            yWaveText     = self.__UiElems.Vertical_Waveform_Combobox.currentText()
            yWaveTable    = self.__WaveTables[yWaveText]
            yStableLUT    = numpy.append(yWaveTable, yWaveTable[0]) # Forces wraparound
            yStableTimes  = numpy.linspace(start = 0, stop = yPeriod, num = yStableLUT.shape[0])
            yScreenLUT, _ = Analysis.normalizeDisplacement(yStableLUT, Const.RES_H)
            yFunction     = lambda times: numpy.interp(x = times, xp = yStableTimes, fp = yScreenLUT, period = yPeriod)

            # Sets filter
            xFilteringText = self.__UiElems.Sampling_Horizontal_Collection_Combobox.currentText()
            xFilterFunction = self.getTimestampFilterFunc(xFilteringText, xFrequency, xScreenLUT, xStableTimes)
            yFilteringText = self.__UiElems.Sampling_Vertical_Collection_Combobox.currentText()
            yFilterFunction = self.getTimestampFilterFunc(yFilteringText, yFrequency, yScreenLUT, yStableTimes)

            # Assigns offset
            xTimeOffset = xPhase * (1.0 / xFrequency)
            yTimeOffset = yPhase * (1.0 / yFrequency)

        else:
            print("Error: Main_updateSamplingReconstruction, bad mode '%s'" % (currentLUTMode))
            return

        # Sets reconstruction functions
        if(xFunction is not None and yFunction is not None):
            self.__registerTh.setDataTranslateX(xFunction)
            self.__registerTh.setDataTranslateY(yFunction)
        else:
            print("Error: Main_updateSamplingReconstruction, bad translation functions")
        # Sets filtering function
        self.__registerTh.setDataFilterX(xFilterFunction)
        self.__registerTh.setDataFilterY(yFilterFunction)
        # Sets time offset
        self.__registerTh.setDataOffsetX(xTimeOffset)
        self.__registerTh.setDataOffsetY(yTimeOffset)
        # Sets axis labels
        if self.__currentXLength != xTotalLength:
            self.__updateXAxisLabel(xTotalLength)
            self.__currentXLength = xTotalLength

        if self.__currentYLength != yTotalLength:
            self.__updateYAxisLabel(yTotalLength)
            self.__currentYLength = yTotalLength

    #
    # Description:
    #   Increases sampling frequency.
    #
    def updateSamplingFrequency(self):
        newFrequency = self.__UiElems.Sampling_Frequency_Spinbox.value() * 1000.0 # Spinbox shows kHz, need to provide hz
        self.__MCUInterface.setAdcFrequency(newFrequency)
        if(self.__MCUInterface.isScanning()):
            self.__MCUInterface.pauseEvents()
            self.__MCUInterface.beginEvents()

if __name__ == "__main__":
    if doChangeCPU:
        P = psutil.Process()
        P.cpu_affinity([0])
   # P.nice(psutil.HIGH_PRIORITY_CLASS)
    if not QApplication.instance():
        app = QApplication(sys.argv)
    else:
        app = QApplication.instance()
    window = TestBench()
    window.show()
    exitStatus = app.exec_()
    sys.exit(exitStatus)

Version 2.0/Software/Main/QT Application v4/AWESEM_Testbench_Main.py


import os
import random
from tools import transforms as T
import torch.utils.data as data
from torchvision.transforms import functional as F

import os.path as osp
import pandas as pd
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt

import torchvision
from sklearn.cluster.k_means_ import KMeans

voc_annotation_template = """
<annotation>
    <folder>{}</folder>
    <filename>{}</filename>
    <size>
        <width>{}</width>
        <height>{}</height>
        <depth>{}</depth>
    </size>
    {}
    <segmented>0</segmented>
</annotation>
"""
voc_object_template = """
    <object>
        <name>{}</name>
        <bndbox>
            <xmin>{}</xmin>
            <xmax>{}</xmax>
            <ymin>{}</ymin>
            <ymax>{}</ymax>
        </bndbox>
        <truncated>0</truncated>
        <difficult>0</difficult>
    </object>
"""
# label转换
# label_dict = {
#     '不带电芯充电宝': 'coreless',
#     '带电芯充电宝': 'core',
# }
label_dict = {
    '带电芯充电宝': 1,
    '不带电芯充电宝': 2,
}

def calc_anchors():
    """
    计算anchors
    :return:
    """
    ratios = []
    sizes = []
    bbx = []
    root = '/media/hvt/95f846d8-d39c-4a04-8b28-030feb1957c6/dataset/充电宝/遮挡问题/core'
    for label_file_path in sorted(os.listdir(osp.join(root, 'Annotation'))):
        labels = pd.read_csv(osp.join(root, 'Annotation', label_file_path), delimiter=' ', header=None).values
        for label in labels:
            _, name, xmin, ymin, xmax, ymax = label
            if name not in label_dict.keys() or ymax - ymin < 2:
                # print(label_file_path, label)
                continue

            ratios.append((xmax - xmin) / (ymax - ymin))
            sizes.append([xmax - xmin, ymax - ymin])
            bbx.append([xmin, ymin, xmax, ymax])
    # 聚类分析anchor
    cluster = KMeans(n_clusters=3).fit(sizes)
    # 两种分辨率
    print(cluster.cluster_centers_ * (1333 / 2000))
    cluster = KMeans(n_clusters=2).fit(np.array(ratios).reshape(-1, 1))
    print(cluster.cluster_centers_)

    plt.hist(ratios, bins=10)
    plt.show()
    plt.hist2d(np.array(sizes)[:, 0], np.array(sizes)[:, 1], bins=20)
    plt.show()
    # bbx = np.array(bbx)
    # plt.hist(bbx[:, 1])
    # plt.show()
    # print(np.max(bbx, axis=0))
    # print(np.min(bbx, axis=0))

    # # 查看标定框分布
    # centers_x = labels[:, 1] + (labels[:, 5] - labels[:, 1]) / 2
    # centers_y = labels[:, 2] + (labels[:, 6] - labels[:, 2]) / 2
    # dist, edges = np.histogram(centers_x, bins=50)
    # plt.subplot(2, 1, 1)
    # # print(edges, dist)
    # plt.scatter(edges[:-1], dist)
    # plt.xlim(0, 1200)
    #
    # plt.subplot(2, 1, 2)
    # dist, edges = np.histogram(centers_y, bins=50)
    # # print(edges, dist)
    # plt.scatter(edges[:-1], dist)
    # plt.xlim(0, 1000)
    # plt.show()

def convert_to_voc():
    """
    将充电宝数据集转换为voc格式
    :return:
    """
    categories = set()
    root = '/media/hvt/95f846d8-d39c-4a04-8b28-030feb1957c6/dataset/充电宝/遮挡问题/core'
    img_sizes = []
    # 读取所有label
    for label_file_path in sorted(os.listdir(osp.join(root, 'Annotation'))):
        print(label_file_path)
        labels = pd.read_csv(osp.join(root, 'Annotation', label_file_path), delimiter=' ', header=None).values
        objects = ''
        for label in labels:
            # 只检测两类目标
            if label[1] not in label_dict.keys(): continue
            objects += '\n' + voc_object_template.format(
                label_dict[label[1]],
                label[2], label[4], label[3], label[5],
            )
            categories.add(label[1])
        # 　图片尺寸
        img_name = label_file_path[:-4] + '.jpg'
        img_size = Image.open(osp.join(root, 'Image', img_name)).size
        # 拼接label xml
        xml = voc_annotation_template.format(
            './portable',
            label_file_path[:-4] + '.jpg',
            img_size[0], img_size[1], 3,
            objects
        )
        with open('./portable/VOCdevkit/VOC2012/Annotations/{}.xml'.format(label_file_path[:-4]), 'w') as xml_file:
            xml_file.write(xml)

def split_dataset():
    # 读取所有数据集项目
    dataset = [name[:-4] for name in os.listdir('./portable/VOCdevkit/VOC2012/Annotations')]
    # 打乱顺序
    random.shuffle(dataset)
    # 划分
    train_split = dataset[:5500]
    val_split = dataset[5500:]

    pd.DataFrame(train_split).to_csv(
        './portable/VOCdevkit/VOC2012/ImageSets/Main/train.txt', header=False, index=False)
    pd.DataFrame(val_split).to_csv(
        './portable/VOCdevkit/VOC2012/ImageSets/Main/val.txt', header=False, index=False)
    print(len(train_split), len(val_split))

def crop_images():
    root = '/media/hvt/95f846d8-d39c-4a04-8b28-030feb1957c6/dataset/充电宝/遮挡问题/core'
    img_sizes = []
    names = set()
    # 读取所有label
    for img_name in sorted(os.listdir(osp.join(root, 'Image'))):
        names.add(hash(img_name))
        print(len(names))
        print(img_name)
        img = Image.open(osp.join(root, 'Image', img_name))
        # plt.imshow(np.array(img)[300:850, ...])
        # plt.show()
        # exit()
        img_size = img.size
        img_sizes.append(list(img_size))
    img_sizes = np.array(img_sizes)
    plt.hist(img_sizes[:, 0], bins=20)
    plt.show()
    plt.hist(img_sizes[:, 1], bins=20)
    plt.show()

class PortableDataset(torchvision.datasets.VOCDetection):
    """
    充电宝数据集
    """

    def __init__(self, root, image_set, transforms):
        super().__init__(root, image_set=image_set, transforms=transforms)

    def __getitem__(self, index):
        img, target = super().__getitem__(index)
        return img, target

class PortableSubmitDataset(data.Dataset):
    """
    充电宝submit数据集
    """

    def __init__(self, root, imagesetfile, crop_y_min=300, crop_y_max=1000):
        with open(imagesetfile) as file:
            self.imageset = [osp.join(root, line.replace('\n', '') + '.jpg') for line in file.readlines()]
        self.crop_y_min, self.crop_y_max = crop_y_min, crop_y_max
        print('Submission set size: ', len(self.imageset))

    def __getitem__(self, index):
        # 返回图像和图像id
        # 因为是测试集 不需要返回target
        image = Image.open(self.imageset[index])
        image_id = os.path.split(self.imageset[index])[-1]

        # 裁剪图像白边
        width, height = image.size
        image = image.crop((0, self.crop_y_min, width, self.crop_y_max))

        # 转换为tensor等
        image = F.to_tensor(image)
        return image, image_id

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

def get_portable_dataset(root, image_set, transforms, mode='instances'):
    """
    构建voc格式的充电宝dataset
    :param root: 数据集根目录
    :param image_set: train/val
    :return:
    """
    voc_dataset = PortableDataset(
        root=root,
        image_set=image_set,
        transforms=transforms  # 转换为tensor以及其他augmentation
    )
    return voc_dataset

def get_portable_submit_dataset(root, imagesetfile):
    return PortableSubmitDataset(root, imagesetfile)

if __name__ == '__main__':
    # convert_to_voc()
    # split_dataset()
    # calc_anchors()
    # crop_images()
    # exit()
    transforms = T.Compose([
        T.VocTargetToTorchVision(),
        T.CropWhiteBorder(),
        T.ToTensor(),
    ])
    dataset = get_portable_dataset(
        root='./portable',
        image_set='train',
        transforms=transforms
    )
    print(dataset)
    print(dataset.__getitem__(0)[0].size())


import setuptools

with open("README.md", "r") as fh:
    long_description = fh.read()

setuptools.setup(
    name="printplus",
    version="1",
    author="noidea",
    author_email="noidea1238@gmail.com",
    description="Simple package to print text to terminal with colors and alignment",
    long_description=long_description,
    long_description_content_type='text/markdown',
    packages=setuptools.find_packages(),
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    python_requires='>=3.6',
)


"""
Builds and trains a neural network that uses policy gradients to learn to play Tic-Tac-Toe.

The input to the network is a vector with a number for each space on the board. If the space has one of the networks
pieces then the input vector has the value 1. -1 for the opponents space and 0 for no piece.

The output of the network is a also of the size of the board with each number learning the probability that a move in
that space is the best move.

The network plays successive games randomly alternating between going first and second against an opponent that makes
moves by randomly selecting a free space. The neural network does NOT initially have any way of knowing what is or is not
a valid move, so initially it must learn the rules of the game.

I have trained this version with success at 3x3 tic tac toe until it has a success rate in the region of 75% this maybe
as good as it can do, because 3x3 tic-tac-toe is a theoretical draw, so the random opponent will often get lucky and
force a draw.
"""
import collections
import os
import random

import numpy as np
import tensorflow as tf

from games.tic_tac_toe import TicTacToeGameSpec
from network_helpers import create_network, load_network, get_stochastic_network_move, save_network

HIDDEN_NODES = (100, 80, 60, 40)
BATCH_SIZE = 100  # every how many games to do a parameter update?
LEARN_RATE = 1e-4
PRINT_RESULTS_EVERY_X = 1000  # every how many games to print the results
NETWORK_FILE_PATH = 'current_network.p'  # path to save the network to
NUMBER_OF_GAMES_TO_RUN = 100000

# to play a different game change this to another spec, e.g TicTacToeXGameSpec or ConnectXGameSpec
game_spec = TicTacToeGameSpec()

reward_placeholder = tf.placeholder("float", shape=(None,))
actual_move_placeholder = tf.placeholder("float", shape=(None, game_spec.outputs()))

input_layer, output_layer, variables = create_network(game_spec.board_squares(), HIDDEN_NODES,
                                                      output_nodes=game_spec.outputs())

policy_gradient = tf.reduce_sum(tf.reshape(reward_placeholder, (-1, 1)) * actual_move_placeholder * output_layer)
train_step = tf.train.RMSPropOptimizer(LEARN_RATE).minimize(-policy_gradient)

with tf.Session() as session:
    session.run(tf.initialize_all_variables())

    if os.path.isfile(NETWORK_FILE_PATH):
        print("loading pre-existing network")
        load_network(session, variables, NETWORK_FILE_PATH)

    mini_batch_board_states, mini_batch_moves, mini_batch_rewards = [], [], []
    results = collections.deque(maxlen=PRINT_RESULTS_EVERY_X)

    def make_training_move(board_state, side):
        mini_batch_board_states.append(np.ravel(board_state) * side)
        move = get_stochastic_network_move(session, input_layer, output_layer, board_state, side)
        mini_batch_moves.append(move)
        return game_spec.flat_move_to_tuple(move.argmax())

    for episode_number in range(1, NUMBER_OF_GAMES_TO_RUN):
        # randomize if going first or second
        if bool(random.getrandbits(1)):
            reward = game_spec.play_game(make_training_move, game_spec.get_random_player_func())
        else:
            reward = -game_spec.play_game(game_spec.get_random_player_func(), make_training_move)

        results.append(reward)

        last_game_length = len(mini_batch_board_states) - len(mini_batch_rewards)

        # we scale here so winning quickly is better winning slowly and loosing slowly better than loosing quick
        reward /= float(last_game_length)

        mini_batch_rewards += ([reward] * last_game_length)

        if episode_number % BATCH_SIZE == 0:
            normalized_rewards = mini_batch_rewards - np.mean(mini_batch_rewards)
            normalized_rewards /= np.std(normalized_rewards)

            session.run(train_step, feed_dict={input_layer: mini_batch_board_states,
                                               reward_placeholder: normalized_rewards,
                                               actual_move_placeholder: mini_batch_moves})

            # clear batches
            del mini_batch_board_states[:]
            del mini_batch_moves[:]
            del mini_batch_rewards[:]

        if episode_number % PRINT_RESULTS_EVERY_X == 0:
            print("episode: %s win_rate: %s" % (episode_number, 0.5 + sum(results) / (PRINT_RESULTS_EVERY_X * 2.)))
            save_network(session, variables, NETWORK_FILE_PATH)

    save_network(session, variables, NETWORK_FILE_PATH)


from nutils import solver, mesh, function, cache, types, numeric
from . import *
import numpy, contextlib, tempfile

@contextlib.contextmanager
def tmpcache():
  with tempfile.TemporaryDirectory() as tmpdir:
    with cache.enable(tmpdir):
      yield

def _test_recursion_cache(testcase, solver_iter):
  read = lambda n: tuple(item for i, item in zip(range(n), solver_iter()))
  reference = read(5)
  for lengths in [1,2,3], [1,3,2], [0,3,5]:
    with tmpcache():
      for i, length in enumerate(lengths):
        with testcase.subTest(lengths=lengths, step=i):
          testcase.assertEqual(read(length), reference[:length])

class laplace(TestCase):

  def setUp(self):
    super().setUp()
    domain, geom = mesh.rectilinear([8,8])
    basis = domain.basis('std', degree=1)
    self.cons = domain.boundary['left'].project(0, onto=basis, geometry=geom, ischeme='gauss2')
    dofs = function.Argument('dofs', [len(basis)])
    u = basis.dot(dofs)
    self.residual = domain.integral((basis.grad(geom) * u.grad(geom)).sum(-1)*function.J(geom), degree=2) \
                  + domain.boundary['top'].integral(basis*function.J(geom), degree=2)

  def test_res(self):
    for name in 'direct', 'newton':
      with self.subTest(name):
        if name == 'direct':
          lhs = solver.solve_linear('dofs', residual=self.residual, constrain=self.cons)
        else:
          lhs = solver.newton('dofs', residual=self.residual, constrain=self.cons).solve(tol=1e-10, maxiter=0)
        res = self.residual.eval(arguments=dict(dofs=lhs))
        resnorm = numpy.linalg.norm(res[~self.cons.where])
        self.assertLess(resnorm, 1e-13)

class navierstokes(TestCase):

  def setUp(self):
    super().setUp()
    domain, geom = mesh.rectilinear([numpy.linspace(0,1,9)] * 2)
    ubasis, pbasis = function.chain([
      domain.basis('std', degree=2).vector(2),
      domain.basis('std', degree=1),
    ])
    dofs = function.Argument('dofs', [len(ubasis)])
    u = ubasis.dot(dofs)
    p = pbasis.dot(dofs)
    viscosity = 1
    self.inertia = domain.integral((ubasis * u).sum(-1)*function.J(geom), degree=5)
    stokesres = domain.integral((viscosity * (ubasis.grad(geom) * (u.grad(geom)+u.grad(geom).T)).sum([-1,-2]) - ubasis.div(geom) * p + pbasis * u.div(geom))*function.J(geom), degree=5)
    self.residual = stokesres + domain.integral((ubasis * (u.grad(geom) * u).sum(-1) * u).sum(-1)*function.J(geom), degree=5)
    self.cons = domain.boundary['top,bottom'].project([0,0], onto=ubasis, geometry=geom, ischeme='gauss2') \
              | domain.boundary['left'].project([geom[1]*(1-geom[1]),0], onto=ubasis, geometry=geom, ischeme='gauss2')
    self.lhs0 = solver.solve_linear('dofs', residual=stokesres, constrain=self.cons)
    self.tol = 1e-10

  def assert_resnorm(self, lhs):
    res = self.residual.eval(arguments=dict(dofs=lhs))
    resnorm = numpy.linalg.norm(res[numpy.isnan(self.cons)])
    self.assertLess(resnorm, self.tol)

  def test_direct(self):
    with self.assertRaises(solver.SolverError):
      self.assert_resnorm(solver.solve_linear('dofs', residual=self.residual, constrain=self.cons))

  def test_newton(self):
    self.assert_resnorm(solver.newton('dofs', residual=self.residual, lhs0=self.lhs0, constrain=self.cons).solve(tol=self.tol, maxiter=2))

  def test_newton_iter(self):
    _test_recursion_cache(self, lambda: ((types.frozenarray(lhs), info.resnorm) for lhs, info in solver.newton('dofs', residual=self.residual, constrain=self.cons)))

  def test_pseudotime(self):
    self.assert_resnorm(solver.pseudotime('dofs', residual=self.residual, lhs0=self.lhs0, constrain=self.cons, inertia=self.inertia, timestep=1).solve(tol=self.tol, maxiter=3))

  def test_pseudotime_iter(self):
    _test_recursion_cache(self, lambda: ((types.frozenarray(lhs), info.resnorm) for lhs, info in solver.pseudotime('dofs', residual=self.residual, lhs0=self.lhs0, constrain=self.cons, inertia=self.inertia, timestep=1)))

class finitestrain(TestCase):

  def setUp(self):
    super().setUp()
    domain, geom = mesh.rectilinear([numpy.linspace(0,1,9)] * 2)
    ubasis = domain.basis('std', degree=2).vector(2)
    u = ubasis.dot(function.Argument('dofs', [len(ubasis)]))
    Geom = geom * [1.1, 1] + u
    self.cons = solver.minimize('dofs', domain.boundary['left,right'].integral((u**2).sum(0), degree=4), droptol=1e-15).solve()
    self.boolcons = ~numpy.isnan(self.cons)
    strain = .5 * (function.outer(Geom.grad(geom), axis=1).sum(0) - function.eye(2))
    self.energy = domain.integral(((strain**2).sum([0,1]) + 20*(function.determinant(Geom.grad(geom))-1)**2)*function.J(geom), degree=6)
    self.residual = self.energy.derivative('dofs')
    self.tol = 1e-10

  def assert_resnorm(self, lhs):
    res = self.residual.eval(arguments=dict(dofs=lhs))
    resnorm = numpy.linalg.norm(res[~self.boolcons])
    self.assertLess(resnorm, self.tol)

  def test_direct(self):
    with self.assertRaises(solver.SolverError):
      self.assert_resnorm(solver.solve_linear('dofs', residual=self.residual, constrain=self.cons))

  def test_newton(self):
    self.assert_resnorm(solver.newton('dofs', residual=self.residual, constrain=self.cons).solve(tol=self.tol, maxiter=7))

  def test_newton_boolcons(self):
    self.assert_resnorm(solver.newton('dofs', residual=self.residual, constrain=self.boolcons).solve(tol=self.tol, maxiter=7))

  def test_newton_iter(self):
    _test_recursion_cache(self, lambda: ((types.frozenarray(lhs), info.resnorm) for lhs, info in solver.newton('dofs', residual=self.residual, constrain=self.cons)))

  def test_minimize(self):
    self.assert_resnorm(solver.minimize('dofs', energy=self.energy, constrain=self.cons).solve(tol=self.tol, maxiter=8))

  def test_minimize_boolcons(self):
    self.assert_resnorm(solver.minimize('dofs', energy=self.energy, constrain=self.boolcons).solve(tol=self.tol, maxiter=8))

  def test_minimize_iter(self):
    _test_recursion_cache(self, lambda: ((types.frozenarray(lhs), info.resnorm) for lhs, info in solver.minimize('dofs', energy=self.energy, constrain=self.cons)))

  def test_nonlinear_diagonalshift(self):
    nelems = 10
    domain, geom = mesh.rectilinear([nelems,1])
    geom *= [2*numpy.pi/nelems, 1]
    ubasis = domain.basis('spline', degree=2).vector(2)
    u = ubasis.dot(function.Argument('dofs', [len(ubasis)]))
    Geom = [.5 * geom[0], geom[1] + function.cos(geom[0])] + u # compress by 50% and buckle
    cons = solver.minimize('dofs', domain.boundary['left,right'].integral((u**2).sum(0), degree=4), droptol=1e-15).solve()
    strain = .5 * (function.outer(Geom.grad(geom), axis=1).sum(0) - function.eye(2))
    energy = domain.integral(((strain**2).sum([0,1]) + 150*(function.determinant(Geom.grad(geom))-1)**2)*function.J(geom), degree=6)
    nshift = 0
    for iiter, (lhs, info) in enumerate(solver.minimize('dofs', energy, constrain=cons)):
      self.assertLess(iiter, 38)
      if info.shift:
        nshift += 1
      if info.resnorm < self.tol:
        break
    self.assertEqual(nshift, 9)

@parametrize
class optimize(TestCase):

  def setUp(self):
    super().setUp()
    self.ns = function.Namespace()
    self.domain, self.ns.geom = mesh.rectilinear([2,2])
    self.ns.ubasis = self.domain.basis('std', degree=1)
    self.ns.u = 'ubasis_n ?dofs_n'
    self.optimize = solver.optimize if not self.minimize else lambda *args, newtontol=0, **kwargs: solver.minimize(*args, **kwargs).solve(newtontol)

  def test_linear(self):
    err = self.domain.boundary['bottom'].integral('(u - 1)^2' @ self.ns, degree=2)
    cons = self.optimize('dofs', err, droptol=1e-15)
    numpy.testing.assert_almost_equal(cons, numpy.take([1,numpy.nan], [0,1,1,0,1,1,0,1,1]), decimal=15)

  def test_nonlinear(self):
    err = self.domain.boundary['bottom'].integral('(u + .25 u^3 - 1.25)^2 d:geom' @ self.ns, degree=6)
    cons = self.optimize('dofs', err, droptol=1e-15, newtontol=1e-15)
    numpy.testing.assert_almost_equal(cons, numpy.take([1,numpy.nan], [0,1,1,0,1,1,0,1,1]), decimal=15)

  def test_nonlinear_multipleroots(self):
    err = self.domain.boundary['bottom'].integral('(u + u^2 - .75)^2' @ self.ns, degree=2)
    cons = self.optimize('dofs', err, droptol=1e-15, lhs0=numpy.ones(len(self.ns.ubasis)), newtontol=1e-10)
    numpy.testing.assert_almost_equal(cons, numpy.take([.5,numpy.nan], [0,1,1,0,1,1,0,1,1]), decimal=15)

  def test_nanres(self):
    err = self.domain.integral('(sqrt(1 - u) - .5)^2' @ self.ns, degree=2)
    dofs = self.optimize('dofs', err, newtontol=1e-10)
    numpy.testing.assert_almost_equal(dofs, .75)

optimize(minimize=False)
optimize(minimize=True)

class burgers(TestCase):

  def setUp(self):
    ns = function.Namespace()
    domain, ns.x = mesh.rectilinear([10], periodic=(0,))
    ns.basis = domain.basis('discont', degree=1)
    ns.u = 'basis_n ?dofs_n'
    ns.f = '.5 u^2'
    self.residual = domain.integral('-basis_n,0 f d:x' @ ns, degree=2)
    self.residual += domain.interfaces.integral('-[basis_n] n_0 ({f} - .5 [u] n_0) d:x' @ ns, degree=4)
    self.inertia = domain.integral('basis_n u d:x' @ ns, degree=5)
    self.lhs0 = numpy.sin(numpy.arange(len(ns.basis))) # "random" initial vector

  def test_iters(self):
    it = iter(solver.impliciteuler('dofs', residual=self.residual, inertia=self.inertia, lhs0=self.lhs0, timestep=100)) # involves 2-level timestep scaling
    assert numpy.equal(next(it), self.lhs0).all()
    numeric.assert_allclose64(next(it), 'eNpzNBA1NjHuNHQ3FDsTfCbAuNz4nUGZgeyZiDOZxlONmQwU9W3OFJ/pNQAADZIOPA==')

  def test_resume(self):
    _test_recursion_cache(self, lambda: map(types.frozenarray, solver.impliciteuler('dofs', residual=self.residual, inertia=self.inertia, lhs0=self.lhs0, timestep=1)))

  def test_resume_withscaling(self):
    _test_recursion_cache(self, lambda: map(types.frozenarray, solver.impliciteuler('dofs', residual=self.residual, inertia=self.inertia, lhs0=self.lhs0, timestep=100)))


import logging
import os

from fastapi import FastAPI
from tortoise.contrib.fastapi import register_tortoise
from api.route import v1_router, private_router
from api.middleware import HttpLog

def create_application() -> FastAPI:
    # create the FastAPI application
    logger = logging.getLogger("api")
    application = FastAPI(title="Message REST", version="1.0")

    # mount the router
    application.include_router(private_router)
    application.include_router(v1_router)

    # mount the middleware
    application.add_middleware(HttpLog)

    # hook up startup event
    @application.on_event("startup")
    async def startup_event() -> None:
        logger.info(f"Initialing database url ...")
        register_tortoise(
            app,
            db_url=os.getenv("DB_URL"),
            modules={"models": ["db.model"]},
        )
        logger.info("Database initialization done.")

    # hook up shutdown event
    @application.on_event("shutdown")
    async def shutdown_event() -> None:
        logger.info("Shutting down...")

    return application

app = create_application()


from rest_framework import serializers
from lib import constants as const
from apps.post.models import Post, Comment

class CommentSerializer(serializers.ModelSerializer):
	id = serializers.UUIDField(source=const.COMMENT_ID_PROPERTY, read_only=True)

	class Meta:
		model = Comment
		fields = [
			const.ID_PROPERTY,
			const.DESCRIPTION_PROPERTY
		]

	def create(self, validatedData):
		post = Post.objects.get(postId=validatedData[const.POST_ID_PROPERTY])
		return Comment.objects.create(
			description=validatedData[const.DESCRIPTION_PROPERTY], post=post
		)


#!/usr/bin/env python3
import pandas as pd
import collections
import sys
from pathlib import Path
import argparse

parser = argparse.ArgumentParser(description='Get per user statistics')
parser.add_argument('-i','--ls', help='input file: output file from "spacesaver ls" command', required=True)
parser.add_argument('-p','--peruserbytes', help='output file: name of per user bytes output file', required=True)
parser.add_argument('-l','--largedups', help='output file: list of large duplicates per user', required=True)
parser.add_argument('-d','--dist', help='output file: age distribution of files owned by each user', required=True)
args = vars(parser.parse_args())

pd.set_option('display.max_columns', None)  # or 1000
pd.set_option('display.max_rows', None)  # or 1000
pd.set_option('display.max_colwidth', None)  # or 199

#infile=sys.argv[1]
#infile="/data/CCBR/dev/spacesavers/logs/all_lss.tsv"
#outfile="bytes_per_user.v2.tsv"
infile=args['ls']
outfile=args['peruserbytes']
large_dup_path=args['largedups']
outfile2=args['dist']

# read the file
# file columns
# 0=inode, 1=permissions, 2=owner,
# 3=group, 4=bytes, 5=size, 6=mdate, 7=age,
# 8=file, 9=nduplicates, 10=bduplicates,
# 11=sduplicates, 12=downers, 13=duplicates
x=pd.read_csv(infile,header=0,delimiter="\t",low_memory=False)
x=x.fillna(0)
x=x[x['Bytes']>0]
x=x.reset_index()

large_dup_cutoff=100*1024*1024
# print(x['BDuplicates'])
large_dup=x[x['BDuplicates']>large_dup_cutoff]
filepath = Path(large_dup_path)
filepath.parent.mkdir(parents=True, exist_ok=True)
large_dup.to_csv(filepath,sep="\t",header=True,index=False)

tbites=dict()
dbites=dict()
age_distribution_count=dict()
age_distribution_bites=dict()

users=x.Owner.unique()

for u in users:
    tbites[u]=0
    dbites[u]=0
    age_distribution_count[u]=dict()
    age_distribution_bites[u]=dict()

for i,row in x.iterrows():
    bites=int(row['Bytes'])
    dupbites=int(row['BDuplicates'])
    age=str(row['Age'])
    user=row['Owner']
    # print(row['DOwners'])
    if dupbites!=0:
        downers=collections.Counter(row['DOwners'].split("|"))
        allusers = set(users) | set(downers.keys())
        newusers = allusers - set(users)
        for u in newusers:
            tbites[u]=0
            dbites[u]=0
            age_distribution_count[u]=dict()
            age_distribution_bites[u]=dict()
        users = list(allusers)
    # if not user in age_distribution_count:
    #     continue
    try:
        age_distribution_count[user][age]+=1
        age_distribution_bites[user][age]+=bites
    except KeyError:
        age_distribution_count[user][age]=1
        age_distribution_bites[user][age]=bites

    # try:
    #     tbites[user] += bites
    # except KeyError:
    #     continue
    tbites[user] += bites
    if dupbites==0:
        continue

    for k,v in downers.items():
        dupbites_per_user = bites * v
        dbites[k] += dupbites_per_user
        tbites[k] += dupbites_per_user
        try:
            age_distribution_count[k][age] += v
            age_distribution_bites[k][age] += dupbites_per_user
        except KeyError:
            age_distribution_count[k][age] = v
            age_distribution_bites[k][age] = dupbites_per_user

        # try:
        #     dbites[k] += bites * v
        #     age_distribution_count[k] += v
        # except KeyError:
        #     continue
        # try:
        #     tbites[k] += bites * v
        # except KeyError:
        #     continue

o2=open(outfile2,'w')
o2.write("%s\t%s\t%s\t%s\n"%("Username","Age","Count","Bytes"))

for u in age_distribution_count.keys():
    ages = age_distribution_count[u]
    ages_list = [int(a) for a in ages.keys()]
    # print(u,ages_list)
    maxage = max(ages_list)+1
    for a in range(0,maxage):
        stra = str(a)
        if stra in ages:
            o2.write("%s\t%s\t%s\t%s\n"%(u,stra,age_distribution_count[u][stra],age_distribution_bites[u][stra]))
        else:
            o2.write("%s\t%s\t0\t0\n"%(u,stra))
o2.close()

o=open(outfile,'w')
o.write("%s\t%s\t%s\n"%("User","Total_Bytes","Duplicate_Bytes"))
tbites = collections.OrderedDict(sorted(tbites.items(),key=lambda item:item[1]))
for u,b in tbites.items():
    tb=b
    db=dbites[u]
    o.write("%s\t%d\t%d\n"%(u,tb,db))
o.close()


import requests

class SocialMediaAnalyzer:
    def __init__(self, api_key):
        self.api_key = api_key

    def analyze_user(self, username):
        url = f"https://api.socialmedia.com/v1/user/{username}/analysis"
        headers = {
            "Authorization": f"Bearer {self.api_key}"
        }
        response = requests.get(url, headers=headers)
        data = response.json()

        if response.status_code == 200:
            if "followers" in data:
                followers = data["followers"]
                following = data["following"]
                posts = data["posts"]

                print(f"Social Media Analysis for User: {username}")
                print(f"- Followers: {followers}")
                print(f"- Following: {following}")
                print(f"- Posts: {posts}")
            else:
                print(f"No social media analysis found for user: {username}.")
        else:
            print("Unable to retrieve social media analysis.")

def main():
    api_key = "YOUR_API_KEY"
    username = "example_user"

    analyzer = SocialMediaAnalyzer(api_key)
    analyzer.analyze_user(username)

if __name__ == "__main__":
    main()