#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: victor
@site: http://victor.info/
@email: victor@bai.info
@time: 2022/5/20 13:13
"""
from __future__ import print_function

import os
import sys

sys.path.append(os.path.dirname(os.getcwd()))

import sys
import threading
import grpc
from grpc import RpcError
import numpy as np
from time import time
import tensorflow as tf
from textMG.configs.config import args
from textMG.datasets.dataset import Dataset
from textMG.APIs.api_loggers.api_logger import logger

from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2_grpc

class Inference:
    def __init__(self):
        self.result = {}

    @classmethod
    def doInfer(cls, input, server):
        if len(input) < 1: raise "the input shape[0] should be more then 0"
        if not server.split(':')[-1] == '8500': raise "the serving port should be 8500"
        logger.debug("server is: {}".format(server))
        infer = cls()
        infer(input, server)
        return infer

    def inferFunc(self, input, server):
        ts = time()
        with grpc.insecure_channel(server) as channel:
            stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)

            # create infer request
            request = predict_pb2.PredictRequest()
            request.model_spec.name = 'multi_cnn_category_tf1_serving'
            # request.model_spec.signature_name = 'serving_default'
            request.inputs['input'].CopyFrom(
                tf.compat.v1.make_tensor_proto(input, dtype=float))

            # prediction
            logger.info("Attempting to predict against TF Serving API.")
            try:
                output = stub.Predict.future(request)  # 10 secs timeout
            except RpcError as err:
                logger.critical("stub.Predict.future error occurred: {}".format(err))
                raise

            model_version = output.result().model_spec.version.value
            logger.info("model_version used is: {}".format(model_version))
            logits = output.result().outputs['logits_prob'].float_val
            preds = output.result().outputs['prediction'].int64_val

            self.result['logits'] = str(logits)
            self.result['preds'] = str(preds)
            self.result['infer_time'] = '{:.4f}'.format(time()-ts)
            logger.info('time_used for infer: {:.4f}'.format(time()-ts))

    def __call__(self, input, server, *args, **kwargs):
        return self.inferFunc(input, server)

    @property
    def preds(self):
        return self.result

if __name__ == '__main__':
    # load the test data from dataset
    t1 = time()
    dataset = Dataset()
    input, _ = dataset.process_data(args.path_data_dir, args.vocab_file, args.path_stopwords,
                                    n_examples=args.num_tests)
    logger.info("timed used for data loading :{:.4f}s".format(time() - t1))

    t2 = time()
    infer = Inference.doInfer(input, args.server)
    result = infer.preds
    logger.info('num_tests: {}'.format(args.num_tests))
    if args.print_outputs:
        logger.info(result)
    logger.info("time_used: {:.4f}s".format(time() - t2))


from flask import Blueprint, request, make_response, jsonify, abort
from sqlalchemy import asc, desc
from app.models.task import Task
from app import db
from datetime import datetime
import os
import requests

tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks")

def get_validate_model(cls, model_id):
    try:
        model_id = int(model_id)
    except:
        abort(make_response({"message":f"{cls.__name__} {model_id} invalid"}, 400))

    model = cls.query.get(model_id)

    if not model:
        abort(make_response({"message":f"{cls.__name__} {model_id} not found"}, 404))

    return model

@tasks_bp.route("", methods=["GET"])
def read_all_tasks():
    order_by = request.args.get("sort")

    if order_by == 'asc':
        tasks = Task.query.order_by(asc('title')).all()
    elif order_by == 'desc':
        tasks = Task.query.order_by(desc('title')).all()
    else:
        tasks = Task.query.all()

    tasks_list = [task.to_dict() for task in tasks]
    return make_response(jsonify(tasks_list), 200)

@tasks_bp.route("/<task_id>", methods=["GET"])
def read_one_task(task_id):
    task = get_validate_model(Task, task_id)
    current_task_response = {"task": task.to_dict()}

    return make_response(jsonify(current_task_response), 200)

@tasks_bp.route("", methods=["POST"])
def create_task():
    request_body = request.get_json()

    if "title" not in request_body or "description" not in request_body:
        return make_response({
            "details": "Invalid data"
        }, 400)

    new_task = Task(title=request_body["title"],
                        description=request_body["description"])

    db.session.add(new_task) # track this object
    db.session.commit() # any changes that are pending commit those changes as data written in SQL
    current_task_response = {"task": new_task.to_dict()}
    return make_response(jsonify(current_task_response), 201)

@tasks_bp.route("/<task_id>", methods=["PUT"])
def update_task(task_id):
    task = get_validate_model(Task, task_id)

    request_body = request.get_json()
    task.title = request_body["title"]
    task.description = request_body["description"]

    db.session.commit()

    current_task_response = {"task": task.to_dict()}
    return make_response(jsonify(current_task_response), 200)

@tasks_bp.route("/<task_id>", methods=["DELETE"])
def delete_task(task_id):
    task = get_validate_model(Task, task_id)

    db.session.delete(task)
    db.session.commit()

    return make_response({"details": f'Task {task_id} "{task.title}" successfully deleted'}, 200)

@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"])
def update_incompleted_task_to_complete(task_id):
    task = get_validate_model(Task, task_id)

    task.completed_at = datetime.now()

    db.session.commit()

    slack_bot(task)
    current_task_response = {"task": task.to_dict()}
    return make_response(jsonify(current_task_response), 200)

@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"])
def update_completed_task_to_incomplete(task_id):
    task = get_validate_model(Task, task_id)
    task.completed_at = None

    db.session.commit()

    current_task_response = {"task": task.to_dict()}
    return make_response(jsonify(current_task_response), 200)

def slack_bot(task):
    PATH = "https://slack.com/api/chat.postMessage"
    SLACK_API_KEY = os.environ.get("SLACK_API")

    bot_params = {
        "channel": "task-notifications",
        "text": f"Someone just completed the task {task.title}"
    }

    requests.post(PATH, data=bot_params, headers={"Authorization": SLACK_API_KEY})


import time
import Adafruit_GPIO.SPI as SPI
import Adafruit_SSD1306
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
import subprocess

time.sleep(10)

def get_iface_ip_address(iface):
    cmd = "ifconfig %s | grep \"inet \" | awk '{print $2}'" % iface
    ip = subprocess.check_output(cmd, shell = True )
    if len(str(ip).strip()) < 1:
        return "not connected"
    return str(ip)

def is_rogue_alive():
    cmd = "ps cax | grep 'hostapd' | wc -l"
    hostapd = subprocess.check_output(cmd, shell = True )
    return int(hostapd) > 0

def get_rogue_ssid():
    cmd = "grep -E \"^\W*?ssid\" /usr/share/PiRogue/hostapd/hostapd.conf | cut -d '=' -f2"
    ssid = subprocess.check_output(cmd, shell = True )
    if len(str(ssid).strip()) < 1 or not is_rogue_alive():
        return "not configured"
    return str(ssid)

def get_ssh_port():
    cmd = "grep -E \"^\W*?Port\" /etc/ssh/sshd_config | awk '{print $2}'"
    port = subprocess.check_output(cmd, shell = True )
    if len(str(port).strip()) < 1:
        return "not configured"
    return 'SSID: %s' % str(port)

def is_capturing():
    cmd = "ps cax | grep 'mitm\|wireshark' | wc -l"
    capture = subprocess.check_output(cmd, shell = True )
    if int(capture) == 0:
        return "not capturing"
    return 'capturing'

def get_disk_space():
    cmd = "df -h | awk '$NF==\"/\"{printf \"%d/%dGB\", $3,$2}'"
    disk = subprocess.check_output(cmd, shell = True )
    return str(disk)

def get_free_mem():
    cmd = "free -m | awk 'NR==2{printf \"RAM %.2f%%\", $3*100/$2 }'"
    mem = subprocess.check_output(cmd, shell = True )
    return str(mem)

def get_stats():
    return '%s %s'% (get_disk_space(), get_free_mem())

RST = None
DC = 23
SPI_PORT = 0
SPI_DEVICE = 0
disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)
disp.begin()
disp.clear()
disp.display()
width = disp.width
height = disp.height
padding = -2
top = padding
bottom = height-padding
x = 0
font = ImageFont.load_default()
im = Image.open('/usr/share/PiRogue/oled-screen/infos.ppm').convert('1')
top = 0
left = 12
while True:
    try:
        draw = ImageDraw.Draw(im)
        draw.rectangle(((left, 0), (width, height)), fill=255)
        draw.text((left, top+0),     get_stats(), font=font, fill=0)
        draw.text((left, top+10),    get_iface_ip_address('eth0'), font=font, fill=0)
        draw.text((left, top+20),    get_iface_ip_address('wlan0'), font=font, fill=0)
        draw.text((left, top+30),    get_iface_ip_address('wlan1'), font=font, fill=0)
        draw.text((left, top+40),    get_rogue_ssid(), font=font, fill=0)
        draw.text((left, top+52),    is_capturing(), font=font, fill=0)
        disp.image(im)
        disp.display()
    finally:
        del draw
    time.sleep(3.)


import customtkinter as tk

class ApplicationView:
    def __init__(self, fields, save_callback, exit_callback):
        # Create app frame
        self.app = tk.CTk()
        self.app.geometry("720x480")
        self.app.title("Homework")
        self.app.protocol('WM_DELETE_WINDOW', exit_callback)

        # Set system settings
        tk.set_appearance_mode("System")
        tk.set_default_color_theme("blue")

        # Create title label
        self.title = tk.CTkLabel(self.app, text="")
        self.title.pack(padx=10, pady=10)

        # Create input fields
        self.input_vars = {}
        for field in fields:
            label = tk.CTkLabel(self.app, text=field['label'])
            input_var = tk.StringVar()
            link = tk.CTkEntry(self.app, width=350, height=40, textvariable=input_var)
            label.pack()
            link.pack()
            self.input_vars[field['field_name']] = input_var

        # Create buttons
        self.save_button = tk.CTkButton(self.app, text="Mentés", command=save_callback)
        self.exit_button = tk.CTkButton(self.app, text="Kilépés", command=exit_callback)
        self.save_button.pack(pady=10)
        self.exit_button.pack()

    def get_input(self, field_name):
        return self.input_vars[field_name].get()

    def clear_input(self, field_name):
        self.input_vars[field_name].set("")

    def close(self):
        self.app.destroy()

    def run(self):
        self.app.mainloop()


#!/usr/bin/python

import time
import math
from enum import Enum

from smbus2 import SMBus

# A class for representing a 3D vector of some kind:
# eg. a velocity, an acceleration, a coordinate etc.
class Vector:
    def __init__(self):
        self.x = 0
        self.y = 0
        self.z = 0

    def downscale_values(self, scale_factor):
        self.x = 1.0 * self.x / scale_factor
        self.y = 1.0 * self.y / scale_factor
        self.z = 1.0 * self.z / scale_factor

    def to_radians(self):
        self.x = math.radians(self.x)
        self.y = math.radians(self.y)
        self.z = math.radians(self.z)

    def __str__(self):
        return "{}, {}, {}".format(self.x, self.y, self.z)

# For register numbers, see MPU-9250 register map
class ACC_REGISTER(Enum):
    X = 0x3b
    Y = 0x3d
    Z = 0x3f

class GYRO_REGISTER(Enum):
    X = 67
    Y = 69
    Z = 71

def twos_complement(val, bits):
    """ Converts an unsigned number from sensor into a signed number,
        with what used to be the most significant bit signifying signedness"""
    if val >> (bits - 1) & 0b1:
        val = val - (1 << bits)
    return val

class Sensors:
    def __init__(self, channel, addr):
        print("HWLLO")
        self.channel = channel
        self.address = addr
        self.bus = SMBus(self.channel)
        self.a = 0.98  # Complimentary filter coefficient
        self.gyro_LSB = 131  # From datasheet
        self.timestamp = time.time()  # Time of last measurement
        self.reset_angle()

    def reset_angle(self):
        # Call when robot is stationary, calcs current angle entirely from accelerometer
        self.angle = self.calc_accel_angle()
        return self.angle
        print("resetting angle, new:", self.angle)

    def twobyte_merge(self, register):
        """ Returns: numerical value from high and low byte
            register: the register for the high byte
            assumption: the low byte is stored in the subsequent register
            """
        #raw_data = self.bus.read_i2c_block_data(self.address, register.value, 2)
        raw_data = [0, 0]
        for i in range(2):
            try:
                raw_data[0] = self.bus.read_byte_data(self.address, register.value)
                raw_data[1] = self.bus.read_byte_data(self.address, register.value)
                break
            except OSError:
                continue
        unsigned_val = (raw_data[0] << 8) + raw_data[1]
        return twos_complement(unsigned_val, 16)

    def read_sensor(self, REGISTER):
        """ Returns: Vector with vals from sensor
            REGISTER: Enum with x, y and z addresses."""
        output = Vector()
        #bus = SMBus(self.channel)  # TODO: have bus permanently open instead
        output.x = self.twobyte_merge(REGISTER.X)
        output.y = self.twobyte_merge(REGISTER.Y)
        output.z = self.twobyte_merge(REGISTER.Z)
        #bus.close()
        return output

    def read_accelerometer(self):
        """ Returns: Vector with vals from sensor"""
        return self.read_sensor(ACC_REGISTER)

    def calc_accel_angle(self):
        reading = self.read_accelerometer()
        if reading.x == 0:
            return 0
        return -math.atan(1.0 * reading.y / reading.x)

    def read_gyroscope(self):
        """ Supposed to return: change of angle in radians/s"""
        measurement = self.read_sensor(GYRO_REGISTER)
        #print(measurement)
        measurement.downscale_values(self.gyro_LSB)
        #print(measurement)
        measurement.to_radians()
        #print(measurement)
        return measurement

    # TODO:
    # Add variables for point values
    # Add method called "update sensors", which reads both gyro and accelerometer,
    # and returns a new angle
    def get_angle(self):
        """ Returns current angle and dt"""
        gyro_z = self.read_gyroscope().z
        # print(gyro_z)
        angle_xy = self.calc_accel_angle()
        # print(math.degrees(angle_xy))
        dt = time.time() - self.timestamp
        #y_n = (1 - self.a) * angle_xy + self.a * self.angle
        self.angle = self.a * (self.angle + gyro_z * dt) + (1 - self.a) * angle_xy
        #self.angle = angle_xy
        self.timestamp = time.time()
        return self.angle, dt

def main():
    channel = 1
    address = 0x68
    sensors = Sensors(channel, address)
    reading = sensors.read_accelerometer()
    print(reading)
    print(sensors.read_gyroscope())

def test_filter():
    channel = 1
    address = 0x68
    sensors = Sensors(channel, address)
    current_angle, dt = sensors.get_angle()
    print(math.degrees(current_angle))
    for x in range(10000):
        current_angle, dt = sensors.get_angle()
        if x % 10:
            print(math.degrees(current_angle))
        time.sleep(0.001)

def test_gyro():
    channel = 1
    address = 0x68
    sensors = Sensors(channel, address)
    print(sensors.read_gyroscope())
    print(sensors.read_accelerometer())

def test_accel():
    channel = 1
    address = 0x68
    sensors = Sensors(channel, address)
    print(sensors.calc_accel_angle())

if __name__ == '__main__':
    #test_filter()
    #test_accel()
    test_gyro()


import argparse
import datetime
import logging
import os
from random import randint
import re
import string
import sys
import time
import xml.etree.ElementTree as ET

try:
    from openpyxl.formatting.rule import FormatObject, IconSet, Rule
    from openpyxl.styles import Alignment, Border, Color, Font, PatternFill, Side
    from openpyxl.styles.fills import FILL_SOLID
    from openpyxl.utils import get_column_letter, column_index_from_string
    from openpyxl import Workbook, load_workbook
except:
    print("You must install the openpyxl Python library to use this script.",
          file=sys.stderr)
    print("Try the command:", file=sys.stderr)
    print("   pip install openpyxl", file=sys.stderr)
    print("For more information: http://openpyxl.readthedocs.io/en/default/",
          file=sys.stderr)
    exit(1)

# --- CONSTANTS ---
_VERSION = '1.0'

# --- KEY CONSTANTS ---
OS = 'OS'
BROWSER = 'BROWSER'
RESULT = 'RESULT'
GRAND = 'GRAND'

# --- EXIT CODEs ---
_EXIT_SUCCESS = 0
_EXIT_ERROR = 1
_EXIT_PROGRAM_ERROR = 2
_EXIT_CONTROL_C_EXCEPTION = 3

# --- LOG FILE REGULAR EXPRESSIONS ---
RE_TEST_NAME = re.compile('esxui(-\w+){3,}')
RE_RESULTS = re.compile('(\d+) spec[s]?, (\d+) failure[s]?(, (\d+) pending spec[s]?)?')
RE_FINISHED = re.compile('Finished in [\d.]+ seconds')
RE_TEST_FAIL_START = re.compile('Failures:$')
RE_TEST_PEND_START = re.compile('Pending:$')
RE_TEST_DATA_LINE = re.compile('\d+\)\s+(.*)')
RE_BROWSER_CHROME = re.compile('(chrome)', re.IGNORECASE)
RE_BROWSER_FIREFOX = re.compile('(firefox)', re.IGNORECASE)
RE_BROWSER_IE = re.compile('(iexplorer)', re.IGNORECASE)
RE_BROWSER_SAFARI = re.compile('(safari)', re.IGNORECASE)
RE_VIC_FLEX_TEST_FAIL_START = re.compile('FAILED: execute')
RE_VIC_FLEX_TEST_PASS_START = re.compile('PASSED: execute')
RE_VIC_H5_TEST_FAIL_START = re.compile('.*Vannyo reports \'FAILED\' for test \'(.*)\'')
RE_VIC_H5_TEST_PASS_START = re.compile('.*Vannyo reports \'PASSED\' for test \'(.*)\'')

# ------------------------------------------------------------------------------
class XLSX(object):

    # ---- OS NAMES ----
    OS_UBUNTU = "Ubuntu"
    OS_WINDOWS = "Windows"
    OS_MACOSX = "Mac"
    OS_BLANK = ""
    OS_NAMES = [
        OS_UBUNTU,
        OS_WINDOWS,
        OS_MACOSX,
        OS_BLANK
    ]

    # ---- BROWSER NAMES ----
    BROWSER_CHROME = "Chrome"
    BROWSER_FIREFOX = "Firefox"
    BROWSER_IE = "IE11"
    BROWSER_SAFARI = "Safari"
    BROWSER_NAMES = (
        BROWSER_CHROME,
        BROWSER_FIREFOX,
        BROWSER_IE,
        BROWSER_SAFARI
    )

    # ---- OS/BROWSER LAYOUT ----
    BROWSER_SUBTOTAL = 'Test Subtotals'
    LAYOUT = [
        {OS_UBUNTU:     [BROWSER_CHROME, BROWSER_FIREFOX]},
        {OS_WINDOWS:    [BROWSER_CHROME, BROWSER_FIREFOX, BROWSER_IE]},
        {OS_MACOSX:     [BROWSER_CHROME, BROWSER_FIREFOX, BROWSER_SAFARI]},
        {OS_BLANK:      [BROWSER_SUBTOTAL]}     # Hack for subtotals
    ]

    # ---- TEST RESULT CLASSES ----
    RESULT_TOTAL = "Total"
    RESULT_FAIL = "Fail"
    RESULT_PCT = "% Pass"
    RESULT_NAMES = (
        RESULT_TOTAL,
        RESULT_FAIL,
        RESULT_PCT
    )

    # ---- TEST NAMES ----
    TEST_NAMES = [
        "18-1-VIC-UI-Installer-60",
        "18-1-VIC-UI-Installer-65",
        "18-2-VIC-UI-Uninstaller-60",
        "18-2-VIC-UI-Uninstaller-65",
        "18-3-VIC-UI-Upgrader-60",
        "18-3-VIC-UI-Upgrader-65",
        "18-4-VIC-UI-Plugin-tests-60",
        "18-4-VIC-UI-Plugin-tests-65"
    ]

    GRAYOUT_LAYOUT = [
        {OS_UBUNTU: {
            BROWSER_CHROME: [],
            BROWSER_FIREFOX: TEST_NAMES[0:6]
        }},
        {OS_WINDOWS: {
            BROWSER_CHROME: TEST_NAMES[0:6],
            BROWSER_FIREFOX: TEST_NAMES[0:6],
            BROWSER_IE: TEST_NAMES[0:6]
        }},
        {OS_MACOSX: {
            BROWSER_CHROME: [],
            BROWSER_FIREFOX: TEST_NAMES[0:6],
            BROWSER_SAFARI: TEST_NAMES[0:6]
        }}
    ]

    # ---- SUBTOTAL ROW NAMES ----
    SUBTOTAL_NAMES = [
        "Browser Totals:",
        "OS Totals:",
        "Grand Total:"
    ]

    # ---- BORDER FORMATTING ----
    THIN = Side(border_style="thin", color="000000")
    THICK = Side(border_style="thick", color="000000")
    THIN_BORDER = Border(top=THIN, left=THIN, right=THIN, bottom=THIN)
    THICK_BORDER = Border(top=THICK, left=THICK, right=THICK, bottom=THICK)

    # ---- GAP FORMATTING (OK to resize) ----
    WIDTH_ID = 3.0
    WIDTH_OS_GAP = 1.0

    # ---- FIELD WIDTH FORMATTING (NOT OK to make smaller) ----
    WIDTH_TEST = len(max(TEST_NAMES, key=len))
    WIDTH_RESULT = len(max(RESULT_NAMES, key=len))

    # ---- VERTICAL PLACEMENT CONSTANTS (OK to change) ----
    ROW_OS = 1
    ROW_BROWSER = ROW_OS + 1
    ROW_TITLES = ROW_OS + 2
    ROW_TESTS = ROW_OS + 3

    # ---- COLORS: NAMED (OK to change) ----
    COLOR_WHITE = 0xFFFFFF
    COLOR_BLACK = 0x000000
    COLOR_GREEN = 0x00FF00
    COLOR_YELLOW = 0xFFFF00
    COLOR_RED = 0xFF0000
    COLOR_DKGRAY = 0x404040
    COLOR_OS_UBUNTU_ORANGE = 0xE95420   # Ubuntu Orange
    COLOR_OS_WINDOWS_BLUE = 0x396DA6    # Microsoft Blue
    COLOR_OS_MAC_SILVER = 0xE0E0E0      # Apple Silver(-ish)
    COLOR_ID_FONT = COLOR_WHITE
    COLOR_ID_BKGD = COLOR_DKGRAY
    COLOR_TEST_FONT = COLOR_WHITE
    COLOR_TEST_BKGD = COLOR_DKGRAY
    COLOR_BROWSER_CHROME = 0x4285F4
    COLOR_BROWSER_FIREFOX = 0xD37D2B
    COLOR_BROWSER_IE = 0x0E4678
    COLOR_BROWSER_SAFARI = 0x0E8596

    # ---- COLORS: OS HEADINGS (OK to change) ----
    COLOR_OS = {
        OS_UBUNTU:  COLOR_OS_UBUNTU_ORANGE,
        OS_WINDOWS: COLOR_OS_WINDOWS_BLUE,
        OS_MACOSX:  COLOR_OS_MAC_SILVER,
        OS_BLANK:   COLOR_WHITE
    }

    # ---- COLORS: BROWSER HEADINGS (OK to change) ----
    # Ref: http://paletton.com/#uid=73D0u0kllllaFw0g0qFqFg0w0aF
    COLOR_BROWSER = {
        BROWSER_CHROME:     {'fill': COLOR_BROWSER_CHROME,
                             'font': COLOR_WHITE},
        BROWSER_FIREFOX:    {'fill': COLOR_BROWSER_FIREFOX,
                             'font': COLOR_WHITE},
        BROWSER_IE:         {'fill': COLOR_BROWSER_IE,
                             'font': COLOR_WHITE},
        BROWSER_SAFARI:     {'fill': COLOR_BROWSER_SAFARI,
                             'font': COLOR_WHITE},
        BROWSER_SUBTOTAL:   {'fill': COLOR_BLACK, 'font': COLOR_WHITE},
    }

    # ---- COLORS: RESULT HEADINGS (OK to change, but please don't) ----
    COLOR_RESULT = {
        RESULT_TOTAL:   COLOR_WHITE,
        RESULT_FAIL:    COLOR_RED,
        RESULT_PCT:     COLOR_GREEN
    }

    # ---- COLORS: SUBTOTALs (OK to change) ----
    COLOR_SUBTOTAL = {
        BROWSER:  {'fill': 0x8b4513, 'font': COLOR_WHITE},
        OS:       {'fill': 0xcd853f, 'font': COLOR_WHITE},
        GRAND:    {'fill': 0xdeb887, 'font': COLOR_WHITE}
    }

    # ---- COLORS: TEST THRESHOLDS (OK to change the Green and Yellow values) ----
    # WARNING: THIS WAS TRICKY TO GET RIGHT - CHANGE AT YOUR OWN RISK
    THRESHOLD_GREEN = 0.97     # Test run is green if value >= this
    THRESHOLD_YELLOW = 0.90    # Test run is yellow  if value >= this
    THRESHOLD_RED = 0.0      # Test run is red if value >= this

    COLOR_VALUE_THRESHOLDS = {
        THRESHOLD_GREEN:    COLOR_GREEN,
        THRESHOLD_YELLOW:   COLOR_YELLOW,
        THRESHOLD_RED:      COLOR_RED
    }

    RFO1 = FormatObject(type='num', val=THRESHOLD_RED)
    RFO2 = FormatObject(type='num', val=THRESHOLD_YELLOW)
    RFO3 = FormatObject(type='num', val=THRESHOLD_GREEN)
    RESULT_IS = IconSet(iconSet='3TrafficLights1',
                        cfvo=[RFO1, RFO2, RFO3],
                        showValue=None,
                        percent=None,
                        reverse=None)
    RESULT_RULE = Rule(type='iconSet', iconSet=RESULT_IS)
    RESULT_FORMAT = '0.0%'

    RESULT_FORMULA = '''
=IF(
    OR(
        ISBLANK(INDIRECT("C[-2]",FALSE)),
        INDIRECT("C[-2]",FALSE)=0
    ),
    "",
    (1-(INDIRECT("C[-1]",FALSE)/INDIRECT("C[-2]",FALSE)))
)
    '''.strip().replace('\n', '').replace(' ', '').replace(',', ', ')

    # --------------------------------------------------------------------
    # -- METHODSx
    # --------------------------------------------------------------------
    def __init__(self, wbName='wb_'+str(int(time.time()))+'.xlsx'):
        self.__wbName = wbName
        self.__wb = None
        self.create()

    # --------------------------------------------------------------------
    @property
    def wbName(self):
        return self.__wbName

    # --------------------------------------------------------------------
    @property
    def wb(self):
        return self.__wb

    # --------------------------------------------------------------------
    @staticmethod
    def cs(rgbVal):
        ''' Returns an 8-character hex color value for use with openpyxl '''
        return "{:08X}".format(rgbVal)

    # --------------------------------------------------------------------
    @staticmethod
    def color(rgbVal):
        ''' Returns an 8-character hex color value for use with openpyxl '''
        return Color(rgb=XLSX.cs(rgbVal))

    # --------------------------------------------------------------------
    @staticmethod
    def cc2N(str_col):
        ''' Returns the column letter (or letters) to an index '''
        return column_index_from_string(str_col) - 1

    # --------------------------------------------------------------------
    @staticmethod
    def cn2C(idx):
        N = len(string.ascii_uppercase)
        # Common fast case
        if idx >= 0 and idx < N:
            return string.ascii_uppercase[idx]
        return get_column_letter(idx+1)

    # --------------------------------------------------------------------
    @staticmethod
    def ccNext(str_col):
        ''' Returns the next column character for an Excel spreadsheet '''
        return XLSX.cn2C(XLSX.cc2N(str_col) + 1)

    # --------------------------------------------------------------------
    @staticmethod
    def ccPrev(str_col):
        ''' Returns the prev column character for an Excel spreadsheet '''
        return XLSX.cn2C(XLSX.cc2N(str_col) - 1)

    # --------------------------------------------------------------------
    def solidFill(self, rgbVal):
        ''' Returns openpyxl.styles.PatternFill '''
        c = self.color(rgbVal)
        return PatternFill(fill_type=FILL_SOLID, fgColor=c, bgColor=c)

    # --------------------------------------------------------------------
    def setActiveByID(self, idx=0):
        ''' Returns sets the active sheet by index '''
        assert idx >= 0 and idx < len(self.wb.sheetnames)
        self.wb.active = idx

    # --------------------------------------------------------------------
    def setActiveByName(self, name):
        ''' Returns sets the active sheet by name '''
        assert name in self.wb.sheetnames
        self.setActiveByID(self.wb.sheetnames.index(name))

    # --------------------------------------------------------------------
    def layout(self, layoutIdx):
        ''' Returns the OS name and browser list '''
        assert layoutIdx < len(self.LAYOUT), 'layout - index out of range'
        layoutDict = self.LAYOUT[layoutIdx]
        osName = list(layoutDict.keys())[0]
        return (osName, layoutDict[osName])

    # --------------------------------------------------------------------
    def browsersByOS(self, osName):
        ''' Returns the list of browsers for the given OS name '''
        return self.layout(self.OS_NAMES.index(osName))

    # --------------------------------------------------------------------
    @property
    def width(self):
        ''' Returns the width of the current layout '''
        cols = 0
        for i in range(len(self.LAYOUT)):
            (os, b) = self.layout(i)
            cols += len(b)              # Count of browsers for each OS
        blankCols = cols - 1            # Blank col between each browser set
        cols *= 3                       # Each browser comprises 3 columns
        return cols + blankCols + 2     # Two initial cols for ID and Name

    # --------------------------------------------------------------------
    def add(self, testDate, logfile):
        logging.debug("Adding test: %s %s %s %s %d %d" % (
                testDate,
                logfile.test_name_os,
                logfile.test_name_browser,
                logfile.test_name_suite,
                logfile.num_tests,
                logfile.num_fails
                )
        )

        def add_helper(val, valList, typeName):
            uVals = [_.upper() for _ in valList]
            uVal = val.upper()
            if uVal not in uVals:
                found = False
                for i, uval in enumerate(uVals):
                    if uVal.startswith(uval) or uVal.find(uval) >= 0:
                        val = valList[i]
                        found = True
                        break
                if not found:
                    _fmt = "\'%s\' is not a valid %s name: os:{} brwsr:{} test:{}".format(osName, browserName, testName)
                    raise ValueError(_fmt % (val, typeName))
            else:
                val = valList[uVals.index(uVal)]
            return val

        # Match the OS name
        orig = logfile.test_name_os
        osName = add_helper(logfile.test_name_os, self.OS_NAMES, OS)
        logging.debug("%s matched %s" % (orig, osName))

        # Match the Browser name
        orig = logfile.test_name_browser
        browserName = add_helper(logfile.test_name_browser, self.BROWSER_NAMES, 'browser')
        logging.debug("%s matched %s" % (orig, browserName))

        # Match the test name
        orig = logfile.test_name_suite
        testName = add_helper(logfile.test_name_suite, self.TEST_NAMES, 'test')
        logging.debug("%s matched %s" % (orig, testName))

        # Create our tab if necessary
        dt = str(testDate).split()[0]
        if dt not in self.wb.sheetnames:
            self.createTab(dt)
        self.setActiveByName(dt)

        # Find the place for the data
        row = self.ROW_TESTS + self.TEST_NAMES.index(testName)
        # print("Test row for %s: %s" % (testName, row))
        colMap = {}
        col = 2     # Zero-based column for this case
        for idx in range(len(self.LAYOUT)):
            (os, browsers) = self.layout(idx)
            colMap.setdefault(os, dict())
            for browser in browsers:
                colMap[os][browser] = col
                col += 4     # 3 data columns + 1 gap column
        col = colMap[osName][browserName]

        ws = self.wb.get_active_sheet()
        specCellID = "{}{}".format(self.cn2C(col), row)
        failCellID = "{}{}".format(self.cn2C(col+1), row)
        # print("Setting cell [%s] to %d " % (specCellID, specs))
        # print("Setting cell [%s] to %d " % (failCellID, fails))
        ws[specCellID] = int(logfile.num_tests)
        hyperlink_font = Font(name='Monaco', size=10, underline="single", color="FF0000FF")
        ws[specCellID].hyperlink = "%s" % (logfile.log_bundle_fname)
        ws[specCellID].font = hyperlink_font
        ws[failCellID] = int(logfile.num_fails)

        # ---------------------------------------------------------
        # Update the Failure list and Pending test list
        # The headings are in column A and data in column B
        # baseRow is one past the 3 subtotal rows
        # We collect up the existing data, add any new data and rewrite
        HDG_FAILED = 'Failed Tests'
        HDG_PENDING = 'Pending Tests'
        HDG_FILL = {HDG_FAILED: self.COLOR_RED, HDG_PENDING: self.COLOR_YELLOW}

        def collect_data(existing, hdg, row):
            existing.setdefault(hdg, dict())
            while ws['A'+str(row)].value:
                count = ws['A'+str(row)].value
                data = ws['B'+str(row)].value
                (cases, data) = data.split(':', 1)
                cases = cases.strip()
                data = data.strip()
                existing[hdg].setdefault(data, {'Count': 0, 'Cases': set()} )
                existing[hdg][data]['Count'] += int(count)
                for case in cases.split(','):
                    case = case.strip()
                    existing[hdg][data]['Cases'].add(case)
                row += 1
            return row

        def merge_data(existing, hdg, case, map):
            existing.setdefault(hdg, dict())
            for data in map:
                existing[hdg].setdefault(data, {'Count': 0, 'Cases': set()} )
                existing[hdg][data]['Count'] += 1
                existing[hdg][data]['Cases'].add(case)

        # Collect existing data from the worksheet
        currRow = baseRow = self.ROW_TITLES + len(self.TEST_NAMES) + 3 + 2 + 5
        existing = {}
        for i in range(2):      # Loop for Failures and Pending
            hdg = ws['B'+str(currRow)].value  # Get heading or blank
            if not hdg:     # T -> done
                break
            currRow = collect_data(existing, hdg, currRow+1) + 1

        # Clear out the old data
        for row in range(baseRow, currRow+1):
            for col in range(1, 3):
                coordinate = (row, col)
                if coordinate in ws._cells:
                    del ws._cells[coordinate]

        # Merge in data from log file
        case = logfile.test_name_os[0] + logfile.test_name_browser[0]
        merge_data(existing, HDG_FAILED, case, logfile.tests_failed_map)
        merge_data(existing, HDG_PENDING, case, logfile.tests_pending_map)

        # Write out the updated data
        fixedFont = Font(name='Monaco', size=10)
        lastCol = get_column_letter(self.width)
        currRow = baseRow
        for hdg in [HDG_FAILED, HDG_PENDING]:
            if hdg in existing and len(existing[hdg]):
                hdgCell = ws['B'+str(currRow)]
                hdgCell.value = hdg
                hdgCell.fill = self.solidFill(HDG_FILL[hdg])
                ws.merge_cells('B'+str(currRow)+":"+lastCol+str(currRow))
                for data in sorted(existing[hdg]):
                    currRow += 1
                    ws['A'+str(currRow)].value = existing[hdg][data]['Count']
                    cases = ', '.join(sorted(existing[hdg][data]['Cases']))
                    data = '{:30s}'.format(cases) + ': ' + data
                    ws['B'+str(currRow)].value = data
                    ws['B'+str(currRow)].font = fixedFont
                    ws.merge_cells('B'+str(currRow)+":AK"+str(currRow))
                currRow += 1

    # ------------
    def create(self):
        assert self.wb is None, "Workbook exists: {}".format(self.wbName)
        wbName = self.wbName
        if os.path.isfile(wbName) and os.access(wbName, os.W_OK):
            logging.info("\'%s\' exists and is writable" % (wbName))
            self.__wb = load_workbook(wbName)
            self.setActiveByID(len(self.wb.sheetnames) - 1)
        elif os.path.isfile(wbName):
            logging.error("File \'%s\' exists but is NOT writable" % (wbName))
            raise AttributeError("File \'%s\' exists but is NOT writable" %
                                 (wbName))
        else:
            logging.info("\'%s\' does not exist, creating..." % (wbName))
            self.__wb = Workbook()
            self.setActiveByID()
            self.save()

    # --------------------------------------------------------------------
    def save(self):
        assert self.wb is not None, "XLSX Class has no workbook name"
        self.wb.active = min(1, len(self.wb.sheetnames)-1)  # If 1 sheet
        self.wb.save(self.wbName)

    # --------------------------------------------------------------------
    def __del__(self):
        ''' If you forgot to save, this saves when the instance dies '''
        if self.wb:
            self.save()
        logging.debug("%s destructor: %s" % (self.__class__, self.wbName))

    # --------------------------------------------------------------------
    def ts2tabName(self, posixTimeStamp):
        ''' Tab names are: '2016-05-01' '''
        # WARNING - changing this format will change the way tabs are sorted!
        tabDate = datetime.date.fromtimestamp(posixTimeStamp)
        tabName = "%04d-%02d-%02d" % (tabDate.year, tabDate.month, tabDate.day)
        return tabName

    # --------------------------------------------------------------------
    def foreach_cell(self, ws, cell_range, **kwargs):
        rows = ws[cell_range]
        for row in rows:
            for cell in row:
                for attrName in kwargs:
                    setattr(cell, attrName, kwargs[attrName])

    # --------------------------------------------------------------------
    def subtotals(self, ws, startCols):

        # -------------
        def subtotal_helper(sumRow, sumCols, sumIdx, col1Idx, colNIdx):
            ''' The formula for the subtotal will go in cell [sumCol, sumRow] '''
            sumCol = sumCols[sumIdx][col1Idx]   # Column is always col1Idx
            # Recursion logic:
            # For this level, the sum is in columns sumCols[sumIdx][col1Idx:colNIdx]
            # To find col1Idx and colNIdx for the recursion, we find the indexes of
            # each pair of letters in this level's sumCols list in the next level's
            # sumCol list.  The range for the recursion is this interval minus 1.
            #
            for i in range(col1Idx, colNIdx):
                colStr1 = sumCols[sumIdx][i]        # First letter pair
                colStrN = sumCols[sumIdx][i+1]      # Next letter pair
                nxtcol1Idx = sumCols[sumIdx + 1].index(colStr1)     # Index @next
                nxtcolNIdx = sumCols[sumIdx + 1].index(colStrN)     # Index @next
                subtotal_helper(sumRow-1, sumCols, sumIdx+1, nxtcol1Idx, nxtcolNIdx)

            # Now insert sum at this level
            sumCells = [[], []]            # Build Total/Fail lists

            # On the last subtotal, we sum the test columns, otherwise we sum sums
            if sumIdx == len(sumCols) - 1:
                row1Idx = self.ROW_TESTS
                rowNIdx = row1Idx + len(self.TEST_NAMES)
                testCol = sumCol
                for j in range(2):
                    for row in range(row1Idx, rowNIdx):
                        cellAddr = "{}{}".format(testCol, row)
                        sumCells[j].append(cellAddr)
                    testCol = self.ccNext(testCol)
            else:
                # Collect the cells for this level
                for i in range(col1Idx, colNIdx):
                    str_col = sumCols[sumIdx][i]
                    for j in range(2):
                        cellAddr = "{}{}".format(str_col, sumRow - 1)
                        sumCells[j].append(cellAddr)
                        str_col = self.ccNext(str_col)

            # --- Total/Fail cells
            for i in range(2):
                sumAddr = "{}{}".format(sumCol, sumRow)
                sum = "=SUM(%s)" % (','.join(sumCells[i]))
                ws[sumAddr].value = sum
                ws[sumAddr].font = Font(color=XLSX.cs(cFont))
                ws[sumAddr].fill = self.solidFill(cFill)
                sumCol = self.ccNext(sumCol)

            # --- '% Pass' cell
            cellAddr = "{}{}".format(sumCol, sumRow)
            cell_range = "{0}:{0}".format(cellAddr)
            self.foreach_cell(ws, cell_range,
                              value=XLSX.RESULT_FORMULA,
                              number_format=XLSX.RESULT_FORMAT,
                              font=Font(color=XLSX.cs(cFont)),
                              fill=self.solidFill(cFill)
            )
            ws.conditional_formatting.add(cell_range, XLSX.RESULT_RULE)
# startCols = {
#     'OS': ['C', 'K', 'W', 'AI'],
#     'BROWSER': ['C', 'G', 'K', 'O', 'S', 'W', 'AA', 'AE', 'AI'],
#     'RESULT': ['C', 'D', 'E', 'G', 'H', 'I', 'K', 'L', 'M', 'O', 'P', 'Q', 'S', 'T', 'U', 'W', 'X', 'Y', 'AA', 'AB', 'AC', 'AE', 'AF', 'AG', 'AI', 'AJ', 'AK'],
# }

        # -------------
        sumCols = []
        sumCols.append(startCols[OS])
        sumCols.append(startCols[BROWSER])
        sumCols.append(startCols[RESULT])
        subtotal_helper(14, sumCols, 0, 0, len(startCols[OS])-1)

    # --------------------------------------------------------------------
    def createTab(self, tabName):
        ''' Initialize a new tab in the workbook '''
        if (self.wb and tabName in self.wb.sheetnames):
            logging.info("Tab \'%s\' is exists..." % (tabName))
            self.setActiveByName(tabName)
            return self.wb[tabName]

        logging.debug("Tab \'%s\' is being created..." % (tabName))

        # Insert this sheet in the right place (keep tabs sorted)
        sheetNames = list(self.wb.sheetnames)
        logging.debug("WB sheets: %s" % (sheetNames))
        sheetNames.append(tabName)
        sheetNames.sort()
        sheetNames.reverse()
        logging.debug("WB.rev(): %s" % (sheetNames))
        i = sheetNames.index(tabName)
        ws = self.wb.create_sheet(tabName, i)
        if 'Sheet' in self.wb.sheetnames:
            self.wb.remove(self.wb['Sheet'])
        self.setActiveByName(tabName)
        logging.debug("WB final: %s" % (self.wb.sheetnames))

        # --- Populate the new tab framework ---
        ws['A1'] = "Created tab:"
        ws['A2'] = str(datetime.datetime.now()).split('.')[0][2:]

        # Set up ID and Test Name
        baseRow = self.ROW_TITLES
        cellID = ws['A' + str(baseRow)]
        cellTest = ws['B' + str(baseRow)]
        cellID.value = 'ID'
        cellID.font = Font(color=XLSX.cs(self.COLOR_ID_FONT))
        cellID.fill = self.solidFill(self.COLOR_ID_BKGD)
        cellTest.value = 'Test Name'
        cellTest.font = Font(color=XLSX.cs(self.COLOR_TEST_FONT))
        cellTest.fill = self.solidFill(self.COLOR_TEST_BKGD)
        for i, testName in enumerate(self.TEST_NAMES):
            ws['A' + str(self.ROW_TESTS + i)] = i + 1
            ws['B' + str(self.ROW_TESTS + i)] = testName
        self.wb.active.column_dimensions['A'].width = self.WIDTH_ID
        self.wb.active.column_dimensions['B'].width = self.WIDTH_TEST
        rt = self.ROW_TITLES
        cell_range = "A{}:B{}".format(rt, rt + len(self.TEST_NAMES))
        self.foreach_cell(ws, cell_range, border=XLSX.THIN_BORDER)

        # Set up OS/Browser/Total-Passed-Failed
        startCols = {OS: [], BROWSER: [], RESULT: []}
        alignHC = Alignment(horizontal="center")
        osCol = 'C'
        row = self.ROW_OS
        # --- OS LOOP ---
        for layoutIdx in range(len(self.LAYOUT)):
            startCols[OS].append(osCol)
            (osName, broswerList) = self.layout(layoutIdx)
            cell = ws[osCol+str(row)]
            cell.value = osName
            cell.fill = self.solidFill(self.COLOR_OS[osName])
            cell.alignment = alignHC
            lastID = ((layoutIdx + 1) == len(self.LAYOUT))

            broswerCol = osCol
            lastBrowser = False
            # --- BROWSER LOOP ---
            for browserName in broswerList:
                startCols[BROWSER].append(broswerCol)
                lastBrowser = (broswerList[-1] == browserName)
                cFont = self.COLOR_BROWSER[browserName]['font']
                cFill = self.COLOR_BROWSER[browserName]['fill']
                cell = ws[broswerCol+str(self.ROW_BROWSER)]
                cell.value = browserName
                cell.font = Font(color=XLSX.cs(cFont))
                cell.fill = self.solidFill(cFill)
                cell.alignment = alignHC

                resultCol = broswerCol
                # --- RESULT LOOP ---
                for resultName in self.RESULT_NAMES:
                    startCols[RESULT].append(resultCol)
                    cell = ws[resultCol+str(self.ROW_TITLES)]
                    cell.value = resultName
                    cell.fill = self.solidFill(self.COLOR_RESULT[resultName])
                    cell.alignment = alignHC
                    resultCol = XLSX.ccNext(resultCol)
                # --- RESULT LOOP END ---

                endCol = XLSX.ccPrev(resultCol)
                merge = "{0}{1}:{2}{1}".format(broswerCol, str(row+1),
                                               XLSX.ccPrev(resultCol))
                ws.merge_cells(merge)
                rt = self.ROW_TITLES
                cell_range = "{}{}:{}{}".format(
                    broswerCol, rt,
                    endCol, rt + len(self.TEST_NAMES)
                )
                self.foreach_cell(ws, cell_range, border=XLSX.THIN_BORDER)
                cell_range = "{}{}:{}{}".format(
                    endCol, rt+1,
                    endCol, rt + len(self.TEST_NAMES) + 1   # Plus 1 for subtot
                )
                self.foreach_cell(ws, cell_range,
                                  value=XLSX.RESULT_FORMULA,
                                  number_format=XLSX.RESULT_FORMAT
                                  )
                ws.conditional_formatting.add(cell_range, XLSX.RESULT_RULE)
                broswerCol = resultCol  # Start next browser at last result col
                if not lastBrowser:   # Leave a thin col after except for last
                    ws.column_dimensions[broswerCol].width = \
                        self.WIDTH_OS_GAP / 2
                    broswerCol = XLSX.ccNext(broswerCol)
            # --- BROWSER LOOP END ---

            merge = "{0}{1}:{2}{1}".format(osCol, row, XLSX.ccPrev(broswerCol))
            ws.merge_cells(merge)
            osCol = broswerCol  # Start next OS after last browser col
            if not lastID:   # Leave a skinny column after except for last
                ws.column_dimensions[osCol].width = self.WIDTH_OS_GAP
                osCol = XLSX.ccNext(osCol)
        # --- OS LOOP END ---

        # --- Insert TEST subtotals ---
        for testIdx in range(len(self.TEST_NAMES)+1):
            row = self.ROW_TESTS + testIdx
            cellList = [[],[]]
            for colIdx in range(len(startCols[BROWSER])-1):
                colC = startCols[BROWSER][colIdx]
                cellList[0].append("{}{}".format(colC, row))
                cellList[1].append("{}{}".format(self.ccNext(colC), row))
            str_col = startCols[BROWSER][-1]
            for i in range(2):
                sumFormula = "=SUM(%s)" % (','.join(cellList[i]))
                cellAddr = "{}{}".format(str_col, row)
                ws[cellAddr].value = sumFormula
                str_col = self.ccNext(str_col)

        sumCols = []
        for i in [OS, BROWSER, RESULT]:
            sumCols.append(startCols[i])

        sumColors = []
        for i in [GRAND, OS, BROWSER]:
            t = (self.COLOR_SUBTOTAL[i]['font'], self.COLOR_SUBTOTAL[i]['fill'])
            sumColors.append(t)

        # --- Subtotal recursion helper ---
        def subtotal_helper(sumRow, sumIdx, col1Idx, colNIdx):
            ''' The formula for the subtotal will go in cell [sumCol, sumRow] '''

            sumCol = sumCols[sumIdx][col1Idx]   # Column is always col1Idx
            # Recursion logic:
            # For this level, the sum is in columns sumCols[sumIdx][col1Idx:colNIdx]
            # To find col1Idx and colNIdx for the recursion, we find the indexes of
            # each pair of letters in this level's sumCols list in the next level's
            # sumCol list.  The range for the recursion is this interval minus 1.
            #
            if sumIdx < len(sumCols)-1:
                for i in range(col1Idx, colNIdx):
                    colStr1 = sumCols[sumIdx][i]        # First letter pair
                    colStrN = sumCols[sumIdx][i+1]      # Next letter pair
                    nxtcol1Idx = sumCols[sumIdx + 1].index(colStr1)     # Index @next
                    nxtcolNIdx = sumCols[sumIdx + 1].index(colStrN)     # Index @next
                    subtotal_helper(sumRow-1, sumIdx+1, nxtcol1Idx, nxtcolNIdx)

            # Now insert sum at this level
            sumCells = [[], []]            # Build Total/Fail lists

            # On the last subtotal, we sum the test columns, otherwise we sum sums
            if sumIdx == len(sumCols) - 1:
                row1Idx = self.ROW_TESTS
                rowNIdx = row1Idx + len(self.TEST_NAMES)
                testCol = sumCol
                for i in range(2):
                    cellAddr = "{0}{1}:{0}{2}".format(testCol, row1Idx, rowNIdx-1)
                    sumCells[i].append(cellAddr)
                    testCol = self.ccNext(testCol)
            else:
                # Collect the cells for this level
                for i in range(col1Idx, colNIdx):
                    str_col = sumCols[sumIdx][i]
                    for j in range(2):
                        cellAddr = "{}{}".format(str_col, sumRow - 1)
                        sumCells[j].append(cellAddr)
                        str_col = self.ccNext(str_col)

            # --- Fill in the sums for the Total & Fail cells
            (cFont, cFill) = sumColors[sumIdx]
            for i in range(2):
                sumAddr = "{}{}".format(sumCol, sumRow)
                sum = "=SUM(%s)" % (','.join(sumCells[i]))
                ws[sumAddr].value = sum
                ws[sumAddr].font = Font(color=XLSX.cs(cFont))
                ws[sumAddr].fill = self.solidFill(cFill)
                sumCol = self.ccNext(sumCol)

            # --- '% Pass' cell
            cellAddr = "{}{}".format(sumCol, sumRow)
            cell_range = "{0}:{0}".format(cellAddr)
            self.foreach_cell(ws, cell_range,
                              value=XLSX.RESULT_FORMULA,
                              number_format=XLSX.RESULT_FORMAT,
                              font=Font(color=XLSX.cs(cFont)),
                              fill=self.solidFill(cFill)
            )
            ws.conditional_formatting.add(cell_range, XLSX.RESULT_RULE)
# startCols = {
#     'OS': ['C', 'K', 'W', 'AI'],
#     'BROWSER': ['C', 'G', 'K', 'O', 'S', 'W', 'AA', 'AE', 'AI'],
#     'RESULT': ['C', 'D', 'E', 'G', 'H', 'I', 'K', 'L', 'M', 'O', 'P', 'Q', 'S', 'T', 'U', 'W', 'X', 'Y', 'AA', 'AB', 'AC', 'AE', 'AF', 'AG', 'AI', 'AJ', 'AK'],
# }
        # --- Prime the subtotal recursion ---
        subtotal_helper(14, 0, 0, len(startCols[OS])-1)

        # --- Prime the subtotal recursion ---
        baseRow = self.ROW_TITLES + len(self.TEST_NAMES) + 1
        for i in range(len(self.SUBTOTAL_NAMES)):
            cell = ws['B' + str(baseRow + i)]
            cell.value = self.SUBTOTAL_NAMES[i]
            cell.alignment = Alignment(horizontal="right")

# ------------------------------------------------------------------------------
class Logfile(object):
    def __init__(self, fname):
        self.__time_init = time.time()  # Time to init this classs
        self.__fname = fname            # File name of log
        self.__f_dt = datetime.datetime.fromtimestamp(os.stat(fname).st_mtime)
        self.__tname = None             # Test name from log
        self.__tname_os = None          # OS name
        self.__tname_browser = None     # Browser name
        self.__tname_suite = None       # Test suite name
        self.__log = None               # Content of log file with bad ASCII out
        self.__time_us = None           # Processing time in µs
        self.__t_fails = {}             # Maps failure name to count
        self.__t_pends = {}             # Maps pending test name to count
        self.__tests = 0                # Tests run
        self.__fails = 0                # Tests failed
        self.__pends = 0             # Tests skipped
        self.__log_bundle_fname = None
        self.__load_file()              # Load the data
        self.__time_init = time.time() - self.__time_init
        logging.info("\t%-10s: %f (ms)" % ("Class init time", self.__time_init * 1000))

    @property
    def file_name(self):
        return self.__fname

    @property
    def file_datetime(self):
        return self.__f_dt

    @property
    def test_name(self):
        return self.__tname

    @property
    def test_name_os(self):
        return self.__tname_os

    @property
    def test_name_browser(self):
        return self.__tname_browser

    @property
    def test_name_suite(self):
        return self.__tname_suite

    @property
    def num_tests(self):
        return self.__tests

    @property
    def num_fails(self):
        return self.__fails

    @property
    def num_pending(self):
        return self.__pends

    @property
    def tests_failed_map(self):
        return self.__t_fails

    @property
    def tests_pending_map(self):
        return self.__t_pends

    @property
    def log(self):
        return self.__log

    @property
    def time_fread(self):
        return self.__time_us

    @property
    def time_init(self):
        return self.__time_init

    @property
    def log_bundle_fname(self):
        return self.__log_bundle_fname

    TEST_MATRIX = {
      XLSX.TEST_NAMES[0]: [XLSX.OS_UBUNTU, XLSX.OS_MACOSX], # VC60
      XLSX.TEST_NAMES[1]: [XLSX.OS_UBUNTU, XLSX.OS_MACOSX], # VC65
      XLSX.TEST_NAMES[2]: [XLSX.OS_UBUNTU, XLSX.OS_MACOSX], # VC60
      XLSX.TEST_NAMES[3]: [XLSX.OS_UBUNTU, XLSX.OS_MACOSX], # VC65
      XLSX.TEST_NAMES[4]: [ # VC60
        {XLSX.OS_UBUNTU: [XLSX.BROWSER_CHROME, XLSX.BROWSER_FIREFOX]},
        {XLSX.OS_MACOSX: [XLSX.BROWSER_CHROME, XLSX.BROWSER_FIREFOX, XLSX.BROWSER_SAFARI]},
        {XLSX.OS_WINDOWS: [XLSX.BROWSER_CHROME, XLSX.BROWSER_FIREFOX]}
      ],
      XLSX.TEST_NAMES[5]: [ # VC65
        {XLSX.OS_UBUNTU: [XLSX.BROWSER_CHROME, XLSX.BROWSER_FIREFOX]},
        {XLSX.OS_MACOSX: [XLSX.BROWSER_CHROME, XLSX.BROWSER_FIREFOX, XLSX.BROWSER_SAFARI]},
        {XLSX.OS_WINDOWS: [XLSX.BROWSER_CHROME, XLSX.BROWSER_FIREFOX, XLSX.BROWSER_IE]}
      ]
    }
    # --------------------------------------------------------------------
    def __del__(self):
        ''' If you forgot to save, this saves when the instance dies '''
        logging.debug("%s destructor..." % (self.__class__))

    def __load_file(self):
        '''
        Return the Specs, Fails, and File Mod Date/Time
        '''
        # Process the file, but catch any errors reading it in...
        logging.info("Processing file: \'%s\'" % (self.__fname))
        t1 = time.time()
        try:
            # We open the file in binary mode to handle the screen color escape
            # codes that get interpreted as bad Unicode characters.
            # Then the .decode() cleans up the bytes by removing any high ASCII
            # values, which is all we need for this processing.
            with open(self.__fname, 'rb') as f:
                self.__log = f.read().decode('ascii', 'replace')
        except Exception as e:
            raise e("Read/Decode error in file: \'%s\'" % (self.__fname))
        self.__time_us = time.time() - t1

        # Find the test name or die
        test_tree = ET.parse(self.__fname)
        self.__tname = "-"

        def set_os_and_browser():
            test_dirname = os.path.dirname(self.__fname).split('/')
            test_dirname = test_dirname[len(test_dirname) - 1].split('-')
            test_dirname_len = len(test_dirname)
            is_browser_info_needed = True
            if test_dirname[1] == "4":
                self.__tname_browser = normalize_browsername(test_dirname[test_dirname_len - 1])
                self.__tname_os = test_dirname[test_dirname_len - 2]
            else:
                # defaulting to chrome, as 18-1, 18-2 and 18-3 do not require browser
                self.__tname_browser = XLSX.BROWSER_CHROME
                self.__tname_os = test_dirname[test_dirname_len - 1]
                is_browser_info_needed = False
            return is_browser_info_needed

        def normalize_browsername(browser):
            m = RE_BROWSER_CHROME.search(browser)
            if m:
                normalized = browser[m.start():m.end()]
                return normalized[0].upper() + normalized[1:]
            m = RE_BROWSER_IE.search(browser)
            if m:
                return XLSX.BROWSER_IE

            return browser[0].upper() + browser[1:]

        def get_log_bundle_name():
            date_string = self.__f_dt.strftime("%m%d%y")
            os_string = "%s" % (self.__tname_os)
            browser_string = "%s" % ("-" + self.__tname_browser if is_browser_info_needed else "")
            return "%s-%s%s/log.html" % (self.__tname_suite, os_string, browser_string)

        is_browser_info_needed = set_os_and_browser()
        self.__tname_suite = "%s-%s" % (test_tree.find("suite").attrib["name"], "60" if "-60-" in self.__fname else "65")  # Test suite name
        self.__log_bundle_fname = get_log_bundle_name()

        stats_all_element = test_tree.find("statistics/total/stat[2]")
        self.__tests = 0 if is_browser_info_needed else (int(stats_all_element.attrib["fail"]) + int(stats_all_element.attrib["pass"]))
        self.__fails = 0 if is_browser_info_needed else int(stats_all_element.attrib["fail"])

        bucket = self.__t_fails

        # if the current suite is 18-4 extract details from from ngc_tests.log
        if is_browser_info_needed:
            log_filename = "ui-test-results/%s-%s-%s/ngc_tests.log" % (self.__tname_suite, self.__tname_os, self.__tname_browser)
            try:
                with open(log_filename, 'rb') as f:
                    log = f.read().decode('ascii', 'replace')
                    suite_failed = False
                    if "-65-" in self.__fname:
                        for line in log.split("\n"):
                            if RE_VIC_H5_TEST_FAIL_START.match(line):
                                suite_failed = True
                                data = "%s (%s - see ngc_tests.log)" % (self.__tname_suite, RE_VIC_H5_TEST_FAIL_START.match(line).groups()[0])
                                bucket.setdefault(data, 0)
                                bucket[data] += 1
                                self.__fails += 1
                                self.__tests += 1
                            elif RE_VIC_H5_TEST_PASS_START.match(line):
                                self.__tests += 1

                    else:
                        found_failed_test = False
                        for line in log.split("\n"):
                            if found_failed_test:
                                data = "%s (%s - see ngc_tests.log)" % (self.__tname_suite, line.strip())
                                bucket.setdefault(data, 0)
                                bucket[data] += 1
                                found_failed_test = False
                                continue
                            if RE_VIC_FLEX_TEST_FAIL_START.match(line):
                                found_failed_test = True
                                suite_failed = True
                                self.__fails += 1
                                self.__tests += 1
                                continue
                            elif RE_VIC_FLEX_TEST_PASS_START.match(line):
                                self.__tests += 1
            except FileNotFoundError as e:
                logging.warning("ngc_tests.log was not found for %s" % self.__tname_suite)

            except Exception as e:
                raise e("Read/Decode error in file: \'%s\'" % (log_filename))

        for test in test_tree.find("suite").iter("test"):
            status_element = test.find("status")
            test_name = test.attrib["name"]
            if test_name == "Run Ngc Tests Project":
                continue

            if status_element.attrib["status"] == "FAIL":
                data = "%s (%s)" % (self.__tname_suite, test_name)
                bucket.setdefault(data, 0)
                bucket[data] += 1

        # Return the relevant data for our log file
        FMT = "\t%-20s: %s"
        logging.info(FMT % ("File name", self.file_name))
        logging.info(FMT % ("File Mod Time", self.file_datetime))
        logging.info(FMT % ("File read time (ms)", str(self.time_fread * 1000)))
        logging.info(FMT % ("Test Name", self.test_name))
        logging.info(FMT % ("OS Name", self.test_name_os))
        logging.info(FMT % ("Browser", self.test_name_browser))
        logging.info(FMT % ("Suite Name", self.test_name_suite))
        logging.info(FMT % ("Tests RUN", self.num_tests))
        logging.info(FMT % ("Tests FAILED", self.num_fails))
        logging.info(FMT % ("Pending tests", self.num_pending))

# ------------------------------------------------------------------------------
def main(args):
    ''' --- Main routine --- '''

    # Get the real names of the logging levels
    LOG_LEVEL_NAMES = []
    for _ in [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR]:
        LOG_LEVEL_NAMES.append(logging.getLevelName(_))

    # Parse the arguments
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "DIR",
        default="/var/lib/jenkins/jobs",
        metavar = "searchdir",
        help="Search this directory for log files to parse"
                        " (default=\'%(default)s\')"
    )
    parser.add_argument(
        "-s", "--skipdir",
        default=["analysis", "demo", "deploy"],
        nargs='+',
        metavar = "str",
        help="Directory paths with this in their names will be skipped"
            " (default=\'%(default)s\')"
    )
    parser.add_argument(
        "-x", "--excel",
        default="log.xlsx",
        metavar = "log.xlsx",
        dest="xlsx",
        help="Specify .xlsx file to update (or create)"
            " (default=\'%(default)s\')"
    )
    parser.add_argument(
        "-f", "--logfile",
        default="log",
        dest="logfile",
        metavar = "log",
        help="Name of log files (default=\'%(default)s\')"
    )
    parser.add_argument(
        "-l", "--log",
        default="WARNING",
        choices=LOG_LEVEL_NAMES,
        dest="logLvl",
        help="Set the logging level (default=\'%(default)s\')"
    )
    parser.add_argument(
        "-d", "--debug",
        default=logging.WARNING,
        action="store_const", dest="loglevel", const=logging.DEBUG,
        help="Enable debugging statements (same as --log DEBUG)"
    )
    parser.add_argument(
        "-v", "--verbose",
        dest="loglevel", action="store_const",  const=logging.INFO,
        help="Be verbose (same as --log INFO)"
    )

    args = parser.parse_args(args)
    assert args.DIR is not None, "--dir: Directory to search must be specified"
    assert args.xlsx is not None, "--excel: Excel file name must be specified"

    # Set up the logger
    lfmt = "%(asctime)s: [%(levelname)-7s: %(funcName)-10s#%(lineno)4d] %(message)s"
    ll = min(args.loglevel, getattr(logging, args.logLvl))
    logging.basicConfig(format=lfmt, datefmt='%y.%m.%d %H:%M:%S', level=ll)
    logging.addLevelName( logging.WARNING, "\033[1;33m%s\033[1;0m" % logging.getLevelName(logging.WARNING))
    logging.addLevelName( logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR))
    logging.debug("DEBUG: Arguments: %s" % (args))

    # Open the output XSLX file, creates it if it does not exist
    xlsx = XLSX(args.xlsx)

    # Process the log file(s).
    logsPassFail = [0, 0]
    logsBad = []

    for dirpath, dirnames, filenames in os.walk(args.DIR):
        # Make the name of a log file in this directory
        targetLog = os.path.join(dirpath, args.logfile)

        logging.debug("%s<%s>|%s<%s>|%s<%s>" %
                      ("target", targetLog,
                       "dirnames", dirnames,
                       "filenames", filenames)
                      )
        # For the directories in 'dirpath', remove any ones we should skip
        tmpdirnames = dirnames
        for skipname in args.skipdir:
            for dirname in tmpdirnames:
                if dirname.find(skipname) >= 0:
                    dirnames.remove(dirname)
        logging.debug("dirnames after: %s" % (dirnames))

        # If the log file isn't in this directory, skip it.
        if not(os.path.exists(targetLog) and os.path.isfile(targetLog)):
            logging.debug("No log file in this directory")
            continue

        #print("Processing file <{}>".format(targetLog))
        try:
            # Parse the log file
            logging.debug("-" * 79)
            logfile = Logfile(targetLog)
            logging.debug("-" * 79)
            dt = logfile.file_datetime
            if dt.year < 2016 or (dt.year == 2016 and dt.month < 5):
                logging.info("Skipping old file: %s" % (dt))
                raise ValueError("File too old to process: %s" % (repr(dt)))
        except ValueError as e:
            logsBad.append(targetLog)
            logsPassFail[1] += 1
            continue

        st = datetime.datetime.strftime(dt, '%Y-%m-%d %H:%M:%S')

        try:
            # Update the Excel file
            xlsx.add(st, logfile)
        except ValueError as e:
            msg = "Bad os/browser/test \'%s\': %s" % (targetLog, e)
            logging.error(msg)
            logsBad.append(targetLog)
            logsPassFail[1] += 1
            continue

        logsPassFail[0] += 1

    logging.debug("Log files pass/fail: {}".format(logsPassFail))

    if logsBad:
        logging.debug("Malformed file names:")
        for i, l in enumerate(sorted(logsBad)):
            logging.debug("  #{:6d}: {}".format(i, l))

    del(xlsx)
    logging.info("Processing complete, exiting...")
    return

# -----------------------------------------------------------------------------
#    MAIN CODE
#
if __name__ == "__main__":
    try:
        sys.exit(main(sys.argv[1:]))
    except KeyboardInterrupt:
        print >> sys.stderr, \
            os.path.splitext(os.path.basename(__file__))[0] + ': interrupted'
        sys.exit(_EXIT_CONTROL_C_EXCEPTION)


from django.test import TestCase, override_settings
from django.core.files.uploadedfile import SimpleUploadedFile

import tempfile

from rest_framework import status
from rest_framework.test import APIClient

from PromoterApp.models import Product, PriceVariation

MEDIA_ROOT = tempfile.mkdtemp()

@override_settings(MEDIA_ROOT=MEDIA_ROOT)
class ProductAPITestCase(TestCase):

    def setUp(self):
        self.client = APIClient()

        with open('media/produtos/camera.jpg', 'rb') as image_file:
            image_data = image_file.read()
            image = SimpleUploadedFile(name='test_image.jpg', content=image_data, content_type='image/jpeg')

            self.product_data = {
                "name": "Produto Teste",
                "ean": "9876543210986",
                "min_price": 10.0,
                "max_price": 20.0,
                "image": image
            }

            self.product = Product.objects.create(
                name="Produto Teste",
                ean="1234567890111",
                min_price=16.0,
                max_price=35.0,
                image=image_file.name
            )

        self.product_id = self.product.id

    def tearDown(self):
        PriceVariation.objects.all().delete()
        Product.objects.all().delete()

    def test_create_product(self):
        response = self.client.post('/api/products/', self.product_data, format="multipart")
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(Product.objects.count(), 2)

    def test_create_product_without_ean(self):
        response = self.client.post('/api/products/', format="multipart", data={
            "name": "Produto Teste",
            "min_price": 13.0,
            "max_price": 18.0,
        })

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.json(), {'ean': ['Este campo é obrigatório.']})

    def test_create_already_created_product(self):
        response = self.client.post('/api/products/', format="multipart", data={
            "name": "Produto Teste",
            "ean": "1234567890111",
            "min_price": 13.0,
            "max_price": 18.0,
        })
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.json(), {'error': 'EAN já existe no banco de dados.'})

    def test_get_product_list(self):
        response = self.client.get('/api/products/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 1)

    def test_get_product_detail(self):
        response = self.client.get(f'/api/products/{self.product_id}/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['id'], self.product_id)

    def test_update_product(self):
        updated_data = {
            "name": "Produto Atualizado",
            "min_price": 15.0
        }
        response = self.client.put(f'/api/products/{self.product_id}/', updated_data, format='multipart')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.product.refresh_from_db()
        self.assertEqual(self.product.name, updated_data["name"])
        self.assertEqual(self.product.min_price, updated_data["min_price"])

    def test_delete_product(self):
        response = self.client.delete(f'/api/products/{self.product_id}/')
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
        self.assertEqual(Product.objects.count(), 0)

    def test_create_price_variation(self):
        price_variation_data = {
            "price": 20.0
        }
        response = self.client.post(f'/api/price-variation/{self.product_id}/', price_variation_data, format='json')
        price_variation_count = PriceVariation.objects.filter(product_id=self.product_id)

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(price_variation_count.count(), 1)

    def test_create_price_variation_max_price_erro(self):
        response = self.client.post(f'/api/price-variation/{self.product_id}/', format='json', data={
            "price": 200.0
        })
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.json(), {'detail': 'O preço do produto não pode ser maior que R$ 35,00'})

    def test_create_price_variation_min_price_erro(self):
        response = self.client.post(f'/api/price-variation/{self.product_id}/', format='json', data={
            "price": 3.0
        })
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.json(), {'detail': 'O preço do produto não pode ser menor que R$ 16,00'})

    def test_create_price_variation_missing_product_id(self):
        price_variation_data = {
            "price": 15.0
        }
        response = self.client.post('/api/products/price_variation/', price_variation_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

    def test_create_price_variation_nonexistent_product(self):
        price_variation_data = {
            "price": 15.0
        }
        response = self.client.post('/api/products/999/price_variation/', price_variation_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)


import struct
from enum import IntEnum
from timeit import main
from typing import Type

class VarType(IntEnum):
    Int = 0
    Float = 1

class TypeSize(IntEnum):
    Int = 4
    Float = 8

class ByteEncodeDecode:
    simple_value_header_size = 2
    array_value_header_size = 6
    simple_value_header = 0
    array_value_header = 1

    @staticmethod
    def to_byte(value):
        if type(value) is int:
            return ByteEncodeDecode.int_to_byte(value)
        elif type(value) is float:
            return ByteEncodeDecode.float_to_byte(value)
        elif type(value) is list and type(value[0]) is int:
            return ByteEncodeDecode.int_list_to_byte(value)
        elif type(value) is list and type(value[0]) is float:
            return ByteEncodeDecode.float_list_to_byte(value)

    @staticmethod
    def int_to_byte(value):
        content =  bytes([ByteEncodeDecode.simple_value_header])
        content += bytes([VarType.Int])
        content += struct.pack('<i', value)
        return content

    @staticmethod
    def float_to_byte(value):
        content =  bytes([ByteEncodeDecode.simple_value_header])
        content += bytes([VarType.Float])
        content += struct.pack('<d', value)
        return content

    @staticmethod
    def int_list_to_byte(value):
        content =  bytes([ByteEncodeDecode.array_value_header])
        content += bytes([VarType.Int])
        content += bytes(struct.pack('<i', len(value)))
        content += struct.pack('<{0}i'.format(len(value)), *value)
        return content

    @staticmethod
    def float_list_to_byte(value):
        content =  bytes([ByteEncodeDecode.array_value_header])
        content += bytes([VarType.Float])
        content += bytes(struct.pack('<i', len(value)))
        content += struct.pack('<{0}d'.format(len(value)), *value)
        return content

    @staticmethod
    def read_from_bytes(buffer):
        result = []
        i = 0
        while i < len(buffer):
            is_array = buffer[i] != 0
            i += 1
            type = VarType(buffer[i])
            i += 1
            if not is_array:
                if type == VarType.Int:
                    result.append(struct.unpack('<i', buffer[i : i + TypeSize.Int])[0])
                    i += TypeSize.Int
                elif type == VarType.Float:
                    result.append(struct.unpack('<d', buffer[i : i + TypeSize.Float])[0])
                    i += TypeSize.Float
            else:
                arrayLen = struct.unpack('<i', buffer[i : i + TypeSize.Int])[0]
                i += TypeSize.Int
                if type == VarType.Int:
                    array = list(struct.unpack('<{0}i'.format(arrayLen), buffer[i : i + (TypeSize.Int * arrayLen)]))
                    result.append(array)
                    i += (arrayLen * TypeSize.Int)
                elif type == VarType.Float:
                    array = list(struct.unpack('<{0}d'.format(arrayLen), buffer[i : i + (TypeSize.Float * arrayLen)]))
                    result.append(array)
                    i += (arrayLen * TypeSize.Float)

        return result

def test():
    int_encoded = ByteEncodeDecode.to_byte(1)
    print(int_encoded)
    int_decoded = ByteEncodeDecode.read_from_bytes(int_encoded)[0]
    print(int_decoded)

    float_encoded = ByteEncodeDecode.to_byte(1.0)
    print(float_encoded)
    float_decoded = ByteEncodeDecode.read_from_bytes(float_encoded)[0]
    print(float_decoded)

    int_list_encoded = ByteEncodeDecode.to_byte([1, 2, 3])
    print(int_list_encoded)
    int_list_decoded = ByteEncodeDecode.read_from_bytes(int_list_encoded)[0]
    print(int_list_decoded)

    float_list_encoded = ByteEncodeDecode.to_byte([1.0, 2.0, 3.0])
    print(float_list_encoded)
    float_list_decoded = ByteEncodeDecode.read_from_bytes(float_list_encoded)[0]
    print(float_list_decoded)

if __name__ == '__main__':
    test()


import cv2
import numpy as np
import matplotlib.pyplot as plt

# Load the image
img = cv2.imread('opencv_logo.png')
img_contours = img.copy()
# Convert to grayscale
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Threshold the image to produce a binary image
ret, thresh = cv2.threshold(img_gray, 150, 255, 0)

# Find the contours
contours, heirarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Draw the contours
cv2.drawContours(img_contours, contours, -1, (153,153,0), 3)

plt.subplot(2,2,1)
plt.title('original')
plt.imshow(img)

plt.subplot(2,2,2)
plt.title('grayscale')
plt.imshow(img_gray, cmap ='gray')

plt.subplot(2,2,3)
plt.title('binary image')
plt.imshow(thresh, cmap ='gray')

plt.subplot(2,2,4)
plt.title('contours')
plt.imshow(img_contours)

plt.show()


import inspect
from dataclasses import dataclass
from functools import wraps
from typing import Iterable, List, Optional, Type, TypeVar

from flask import abort, jsonify, make_response, request
from pydantic import BaseModel, ValidationError

from app.types.checks import RunCheckRequest
from app.types.common import (
    SUPPORTED_COUNTRIES,
    UUID,
    Error,
    ErrorType,
    LocalField,
    SearchRequest,
)

T = TypeVar("T")

@dataclass
class SearchInput:
    country_of_incorporation: str

def _validate_country_of_incorporation(country_code) -> List[Error]:
    errors = []
    if country_code is None:
        errors.append(Error.missing_required_field(LocalField.COUNTRY_OF_INCORPORATION))
    if country_code not in SUPPORTED_COUNTRIES:
        errors.append(Error.unsupported_country())
    return errors

def _validate_demo_check(demo_result, demo_type_label):
    errors = []
    if demo_result is None:
        errors.append(
            Error(
                **{
                    "type": ErrorType.PROVIDER_MESSAGE,
                    "message": f"Live {demo_type_label} is not supported",
                }
            )
        )
    return errors

def validate_search_request(request: SearchRequest) -> List[Error]:
    errors = _validate_country_of_incorporation(
        request.search_input.country_of_incorporation
    )
    errors.extend(_validate_demo_check(request.demo_result, "search"))
    return errors

def validate_check_request(request: RunCheckRequest) -> List[Error]:
    errors = _validate_country_of_incorporation(
        request.check_input.get_country_of_incorporation()
    )
    errors.extend(_validate_demo_check(request.demo_result, "check"))
    return errors

def validate_poll_reference(reference):
    errors = []
    try:
        UUID.parse_obj(reference)
    except ValidationError:
        errors.append(
            [
                Error(
                    type=ErrorType.INVALID_INPUT,
                    message="Reference must be a valid UUID",
                )
            ]
        )
    return errors

def validate_poll_request(request, check_id=None):
    errors = []
    reference = request.reference
    if check_id is not None and check_id != request.id:
        errors.append(
            [
                Error(
                    type=ErrorType.INVALID_CHECK_INPUT,
                    message="ID in URL mismatches check ID in request",
                )
            ]
        )
    if reference is not None:
        errors.extend(validate_poll_reference(reference))
    return errors

def _first(x: Iterable[T]) -> Optional[T]:
    return next(iter(x), None)

def _get_input_annotation(signature: inspect.Signature) -> Optional[Type[BaseModel]]:
    first_param: Optional[inspect.Parameter] = _first(signature.parameters.values())
    if first_param is None:
        return None

    if first_param.kind not in [
        inspect.Parameter.POSITIONAL_ONLY,
        inspect.Parameter.POSITIONAL_OR_KEYWORD,
    ]:
        return None

    if not issubclass(first_param.annotation, BaseModel):
        return None

    return first_param.annotation

def validate_models(fn):
    """
    Creates a Pydantic Model from the request data and validates it.

    Throws DataError if invalid.
    Otherwise, it passes the validated request data to the wrapped function.
    """

    signature = inspect.signature(fn)

    assert issubclass(
        signature.return_annotation, BaseModel
    ), "Must have a return type annotation"
    output_model = signature.return_annotation
    input_model = _get_input_annotation(signature)

    @wraps(fn)
    def wrapped_fn(*args, **kwargs):
        if input_model is None:
            res = fn(*args, **kwargs)
        else:
            model = None
            try:
                model = input_model(**request.json)
            except ValidationError as e:
                abort(
                    make_response(
                        jsonify(
                            {
                                "errors": [
                                    {
                                        "message": f"{error['msg']} {error.get('loc', '')}",
                                        "type": ErrorType.INVALID_INPUT,
                                    }
                                    for error in e.errors()
                                ]
                            }
                        ),
                        400,
                    )
                )

            res = fn(model, *args, **kwargs)

        assert isinstance(res, output_model)

        return res.dict(exclude_none=True)

    return wrapped_fn


import contextlib
import os
import sys
import warnings
from pathlib import Path

from epic.detection.base_mask_generator import BaseMaskGenerator

import numpy as np

import torch
from torch.nn.functional import upsample

CHECKPOINT_DIR = (Path(__file__).parents[2].resolve() / 'misc/checkpoints/'
                  'etos_deepcut')

class DeepExtremeCut(BaseMaskGenerator):
    def __init__(self, device='cpu', model_name='dextr_pascal-sbd', pad=50,
                 thres=0.8):
        # cite
        curr_dir = Path(__file__).resolve().parents[2]
        pkg_dir = os.path.join(curr_dir, 'third_party', 'etos-deepcut')
        sys.path.insert(1, pkg_dir)
        import networks.deeplab_resnet as resnet
        from dataloaders import helpers as helpers

        #  Create the network and load the weights
        with open(os.devnull, "w") as f, contextlib.redirect_stdout(f):
            net = resnet.resnet101(1, nInputChannels=4, classifier='psp')
        state_dict_checkpoint = torch.load(
            os.path.join(CHECKPOINT_DIR, model_name + '.pth'),
            map_location=lambda storage, loc: storage)

        # Remove the prefix .module from the model when it is trained using
        # DataParallel
        if 'module.' in list(state_dict_checkpoint.keys())[0]:
            new_state_dict = {}
            for k, v in state_dict_checkpoint.items():
                name = k[7:]  # remove `module.` from multi-gpu training
                new_state_dict[name] = v
        else:
            new_state_dict = state_dict_checkpoint
        net.load_state_dict(new_state_dict)
        net.eval()
        net.to(device)

        self._net = net
        self._helpers = helpers
        self._pad = pad
        self._thres = thres
        self._device = device

    def gen_mask(self, frame, coords):
        x1, y1, x2, y2 = coords
        centre = ((x2 - x1) / 2 + x1, (y2 - y1) / 2 + y1)

        extreme_points_ori = [[coords[0], centre[1]],
                              [coords[2], centre[1]],
                              [centre[0], coords[1]],
                              [centre[0], coords[3]]]
        extreme_points_ori = np.array([[round(x), round(y)] for x, y in
                                       extreme_points_ori])

        with torch.no_grad():
            bbox = self._helpers.get_bbox(frame, points=extreme_points_ori,
                                          pad=self._pad, zero_pad=True)
            crop_image = self._helpers.crop_from_bbox(frame, bbox,
                                                      zero_pad=True)
            im_size = frame.shape[:2]

            # Crop image to the bounding box from the extreme points and
            # resize
            resize_image = self._helpers.fixed_resize(crop_image, (
                512, 512)).astype(np.float32)

            # Generate extreme point heat map normalized to image values
            extreme_points = extreme_points_ori - [np.min(
                extreme_points_ori[:, 0]), np.min(
                    extreme_points_ori[:, 1])] + [self._pad, self._pad]
            extreme_points = (512 * extreme_points * [
                1 / crop_image.shape[1],
                1 / crop_image.shape[0]]).astype(int)
            extreme_heatmap = self._helpers.make_gt(
                resize_image, extreme_points, sigma=10)
            extreme_heatmap = self._helpers.cstm_normalize(
                extreme_heatmap, 255)

            # Concatenate inputs and convert to tensor
            input_dextr = np.concatenate((resize_image, extreme_heatmap[
                :, :, np.newaxis]), axis=2)
            inputs = torch.from_numpy(input_dextr.transpose((2, 0, 1))[
                np.newaxis, ...])

            # Run a forward pass
            inputs = inputs.to(self._device)
            with warnings.catch_warnings():
                warnings.filterwarnings('ignore', category=UserWarning)
                outputs = self._net.forward(inputs)
                outputs = upsample(outputs, size=(512, 512), mode='bilinear',
                                   align_corners=True)
            outputs = outputs.to(torch.device('cpu'))
            pred = np.transpose(outputs.data.numpy()[0, ...], (1, 2, 0))
            pred = 1 / (1 + np.exp(-pred))
            pred = np.squeeze(pred)

            result = self._helpers.crop2fullmask(
                pred, bbox, im_size=im_size, zero_pad=True,
                relax=self._pad) > self._thres

        return result


#!/usr/bin/python3

import json

f= open("terms.json","r")
j = json.load(f)

f.close()

f=open("queries_training_set.txt","r")
l = f.readlines()
f.close()

o=open("query_terms.txt","w")
for i in range(len(l)):
    a = l[i].split("\n")
    b = a[0].split(" ")
    s=""
    for c in b:
        s+=str(j[c])+","

    o.write(s[:len(s)-1]+"\n")

o.close()


import datetime

time = datetime.datetime.now()
date_time = time.strftime("%m/%d/%Y, %H:%M:%S")
class Operation:
    def __init__(self,balance):
        self.balance = balance
        self.account_change = []
    # main function to deal with credit
    def pullMoney( self, request):
        cash = 0
        papers = [100,50,20,10,5]
        if request <= self.balance :
            if request > 0 :
                for paper in papers:
                    while request >= paper :
                        request -= paper
                        cash += paper
                        self.balance -= paper
            if request < 10 :
                print(f'your request : {request}  \n'
                     f'you must provide more at least 10 $ to preform operation\n')
                cash_request = {
                    "cash":cash,
                    "time":date_time,
                    "balance":self.balance
                }
                self.account_change.append(cash_request)
                print(f'cash : {cash} \nbalance : {self.balance} ')
            else:
                print('can not preform request,pls input at least 10 $')
        else:
            print('sorry u dont have enough credits in you account')
    def show_Changes(self):
        print('your transcripation  details :')
        for operation in self.account_change :
            print(f'your withdraw {operation["cash"]} $ from your account at {operation["time"]}'
                  f' your balance : {operation["balance"]} $')


# -*- coding: utf-8 -*-
import os
import cv2
import numpy as np
from sklearn.metrics import confusion_matrix

class IOUMetric:
    """
    Class to calculate mean-iou using fast_hist method
    """
    def __init__(self, num_classes):
        self.num_classes = num_classes
        self.hist = np.zeros((num_classes, num_classes))
    def _fast_hist(self, label_pred, label_true):
        mask = (label_true >= 0) & (label_true < self.num_classes)
        hist = np.bincount(
            self.num_classes * label_true[mask].astype(int) +
            label_pred[mask], minlength=self.num_classes ** 2).reshape(self.num_classes, self.num_classes)
        return hist

    def evaluate(self, predictions, gts):
        for lp, lt in zip(predictions, gts):
            assert len(lp.flatten()) == len(lt.flatten())
            self.hist += self._fast_hist(lp.flatten(), lt.flatten())
        # miou
        iou = np.diag(self.hist) / (self.hist.sum(axis=1) + self.hist.sum(axis=0) - np.diag(self.hist))
        miou = np.nanmean(iou)
        # mean acc
        acc = np.diag(self.hist).sum() / self.hist.sum()
        acc_cls = np.nanmean(np.diag(self.hist) / self.hist.sum(axis=1))
        freq = self.hist.sum(axis=1) / self.hist.sum()
        fwavacc = (freq[freq > 0] * iou[freq > 0]).sum()
        return acc, acc_cls, iou, miou, fwavacc

def eval_function():
    label_path = 'submits/label/'
    predict_path = 'submits/img/'
    pres = os.listdir(predict_path)
    labels = []
    predicts = []
    for im in pres:
        if im[-4:] == '.png':
            label_name = im.split('.')[0] + '.png'
            lab_path = os.path.join(label_path, label_name)
            pre_path = os.path.join(predict_path, im)
            label = cv2.imdecode(np.fromfile(lab_path,dtype=np.uint8),0)
            pre = cv2.imdecode(np.fromfile(pre_path,dtype=np.uint8),0)
            label[label>0] = 1
            pre[pre>0] = 1
            labels.append(label)
            predicts.append(pre)
    el = IOUMetric(2)
    acc, acc_cls, iou, miou, fwavacc = el.evaluate(predicts, labels)
    print('acc: ',acc)
    print('acc_cls: ',acc_cls)
    print('iou: ',iou)
    print('miou: ',miou)
    print('fwavacc: ',fwavacc)

    pres = os.listdir(predict_path)
    init = np.zeros((2,2))
    for im in pres:
        if im[-4:] == '.png':
            lb_path = os.path.join(label_path, im)
            pre_path = os.path.join(predict_path, im)
            lb = cv2.imdecode(np.fromfile(lb_path,dtype=np.uint8),0)
            pre = cv2.imdecode(np.fromfile(pre_path,dtype=np.uint8),0)
            lb[lb>0] = 1
            pre[pre>0] = 1
            lb = lb.flatten()
            pre = pre.flatten()
            confuse = confusion_matrix(lb, pre)
            init += confuse

    precision = init[1][1]/(init[0][1] + init[1][1])
    recall = init[1][1]/(init[1][0] + init[1][1])
    accuracy = (init[0][0] + init[1][1])/init.sum()
    f1_score = 2*precision*recall/(precision + recall)
    print('class_precision: ', precision)
    print('class_recall: ', recall)
    print('accuracy: ', accuracy)
    print('f1_score: ', f1_score)
if __name__ == "__main__":
    eval_function()

Damaged Road Extraction Based on Simulated Post-Disaster Remote Sensing Image/eval.py


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time : 2019-05-14 21:27
# @Author : lmh
# @Software: PyCharm
#

import openpyxl

class exceldata():
    def __init__(self, excelfile, sheetname):
        self.excelfile = excelfile
        self.sheetname = sheetname
        self.wb = openpyxl.load_workbook(self.excelfile)
        self.ws = self.wb[self.sheetname]
        self.rows = self.ws.max_row
        self.cols = self.ws.max_column

    def exceldata_row(self):
        """
        读取excel,每行数据和第一行组成一个字典.返回excel数据列表
        :return:excel数据列表,每个元素是该行和首行对应的字典
        """
        exceldata = []
        # 读取
        for r in range(1, self.rows+1):
            excelline = []
            for c in range(1, self.cols + 1):
                excelline.append(self.ws.cell(row=r, column=c).value)
            exceldata.append(excelline)

        excellist = []
        for i in range(1, len(exceldata)):
            dict1 = dict(zip(exceldata[0], exceldata[i]))
            excellist.append(dict1)
        return excellist

    def getChars(self, length):
        return [self.getChar(index) for index in range(length)]

    def getChar(self, number):
        factor, moder = divmod(number, 26) # 26 字母个数
        modChar = chr(moder + 65)          # 65 -> 'A'
        if factor != 0:
            modChar = self.getChar(factor-1) + modChar # factor - 1 : 商为有效值时起始数为 1 而余数是 0
        return modChar

    def exceldata_col(self):
        """
        读取excel,每列数据和第一列组成一个字典.返回excel数据列表
        :return:excel数据列表,每个元素是该行和首行对应的字典
        """
        # 读取
        exceldata = []
        for c in range(1, self.cols+1):
            excelline = []
            for r in range(1, self.rows + 1):
                excelline.append(self.ws.cell(row=r, column=c).value)
            exceldata.append(excelline)

        excellist = []
        for i in range(1, len(exceldata)):
            dict1 = dict(zip(exceldata[0], exceldata[i]))
            excellist.append(dict1)
        return excellist


from collections import defaultdict
import sys
sys.setrecursionlimit(10**6)

def solution(nodeinfo):
    num2x = {i+1 : nodeinfo[i][0] for i in range(len(nodeinfo))}
    x2num = {nodeinfo[i][0] : i+1 for i in range(len(nodeinfo))}
    y_cords = set()
    nodelevel = defaultdict(list)
    nodes = [[0] * 2 for _ in range(len(nodeinfo)+1)]

    for info in nodeinfo:
        y_cords.add(info[1])
        nodelevel[info[1]].append(info[0])
    y_cords = sorted(list(y_cords), reverse=True)

    def maketree(num, depth, ll, rl):
        if depth == len(y_cords):
            return

        x = num2x[num]
        for node in nodelevel[y_cords[depth]]:
            if ll <= node <= x:
                nodes[num][0] = x2num[node]
                maketree(x2num[node], depth+1, ll, x)
            elif x <= node <= rl:
                nodes[num][1] = x2num[node]
                maketree(x2num[node], depth+1, x, rl)

    root = x2num[nodelevel[y_cords[0]][0]]
    maketree(root, 1, 0, max(nodeinfo)[0])

    def preorder(n):
        if n == 0:
            return
        pre.append(n)
        preorder(nodes[n][0])
        preorder(nodes[n][1])

    def postorder(n):
        if n == 0:
            return
        postorder(nodes[n][0])
        postorder(nodes[n][1])
        post.append(n)

    pre = []
    post = []
    preorder(root)
    postorder(root)

    return [pre, post]