<commit_before>from msignal.mcircuit import *
import unittest
import sympy as sym
from msignal.mrf import MRF

s = sym.var('s')

class McircuitsTester(unittest.TestCase):
    """Unit tests for mcircuits

    """

    def test_R(self):
        """Mcircuits: check R

        """
        a = R(10)
        self.assertEqual(a.Z, 10, "Z incorrect.")

    def test_L(self):
        """Mcircuits: check L

        """
        a = L(10, 5)
        self.assertEqual(a.Z, 10 * s, "Z incorrect.")
        self.assertEqual(a.V, -10 * 5, "V incorrect.")

    def test_C(self):
        """Mcircuits: check C

        """
        a = C(10, 5)

        self.assertEqual(a.Z, 1 / (10 * s), "Z incorrect.")
        self.assertEqual(a.V, 5 / s, "V incorrect.")

    def test_RplusL(self):
        """Mcircuits: check R + L

        """
        a = R(10) + L(5)
        self.assertEqual(a.Z, 5 * s + 10, "Z incorrect.")
        self.assertEqual(type(a), Thevenin, "type incorrect.")

    def test_Shunt(self):
        """Mcircuits: check Shunt

        """
        a = Shunt(R(10) + V(5))
        self.assertEqual(a.Z1oc, 10, "Z1oc incorrect.")
        self.assertEqual(a.Z2oc, 10, "Z2oc incorrect.")
        self.assertEqual(a.V2oc, 5 / s, "V2oc incorrect.")
        print(a.I2sc)
        self.assertEqual(a.I2sc, 0.5 / s, "I2sc incorrect.")
        self.assertEqual(a.V1oc, 5 / s, "V1oc incorrect.")
        self.assertEqual(a.I1sc, 0.5 / s, "I1sc incorrect.")

        b = a.chain(Shunt(R(30)))
        self.assertEqual(b.Z1oc, 7.5, "Z1oc incorrect.")
        self.assertEqual(b.Z2oc, 7.5, "Z2oc incorrect.")
        self.assertEqual(b.V2oc, 3.75 / s, "V2oc incorrect.")
        self.assertEqual(b.I2sc, 0.5 / s, "I2sc incorrect.")
        self.assertEqual(b.V1oc, 3.75 / s, "V1oc incorrect.")
        self.assertEqual(b.I1sc, 0.5 / s, "I1sc incorrect.")

        c = a.chain(Series(R(30)))
        self.assertEqual(c.Z1oc, 10, "Z1oc incorrect.")
        self.assertEqual(c.Z2oc, 40, "Z2oc incorrect.")
        self.assertEqual(c.V1oc, 5 / s, "V1oc incorrect.")
        self.assertEqual(c.V2oc, 5 / s, "V2oc incorrect.")
        self.assertEqual(c.I1sc, -0.5 / s, "I1sc incorrect.")
        self.assertEqual(c.I2sc, 0 / s, "I2sc incorrect.")

        d = Shunt(R(10)).chain(Series(R(30)))

    def test_Series(self):
        """Mcircuits: check Series

        """
        a = Series(R(10) + V(5))
        self.assertEqual(a.Y1sc, 0.1, "Y1sc incorrect.")
        self.assertEqual(a.Y2sc, 0.1, "Y2sc incorrect.")
        print(a.V1oc)
        self.assertEqual(a.V1oc, 5 / s, "V1oc incorrect.")
        self.assertEqual(a.V2oc, 5 / s, "V2oc incorrect.")
        self.assertEqual(a.I1sc, 0.5 / s, "I1sc incorrect.")
        self.assertEqual(a.I2sc, -0.5 / s, "I2sc incorrect.")

    def test_LSection(self):
        """Mcircuits: check LSection

        """
        a = LSection(R(10), R(30))

        self.assertEqual(a.Vgain12, 0.75, "Vgain12 incorrect.")
        self.assertEqual(a.Igain12, -1, "Igain12 incorrect.")
        self.assertEqual(a.Z1oc, 40, "Z1oc incorrect.")
        self.assertEqual(a.Z2oc, 30, "Z2oc incorrect.")
        self.assertEqual(a.Z1sc, 10, "Z1sc incorrect.")
        self.assertEqual(a.Z2sc, 7.5, "Z2sc incorrect.")
        self.assertEqual(a.Vgain(1, 2), 0.75, "Vgain 1->2 incorrect.")
        self.assertEqual(a.Vgain(2, 1), 1, "Vgain 2->1 incorrect.")
        self.assertEqual(a.Igain(1, 2), -1, "Igain 1->2 incorrect.")
        self.assertEqual(a.Igain(2, 1), -0.75, "Igain 2->1 incorrect.")
        self.assertEqual(a.Vresponse(V(1), 1, 2), 0.75 / s, "Vresponse 1->2 incorrect.")
        self.assertEqual(a.Vresponse(V(1), 2, 1), 1 / s, "Vresponse 2->1 incorrect.")
        self.assertEqual(a.Iresponse(I(1), 1, 2), -1 / s, "Iresponse 1->2 incorrect.")
        self.assertEqual(a.Iresponse(I(1), 2, 1), -0.75 / s, "Iresponse 2->1 incorrect.")

        b = a.load(R(30))
        self.assertEqual(type(b), Thevenin, "type incorrect.")
        self.assertEqual(b.Z, 25, "R loaded Z incorrect.")
        self.assertEqual(b.V, 0, "R loaded V incorrect.")

        c = a.load(R(30) + V(5))
        self.assertEqual(type(b), Thevenin, "type incorrect.")
        self.assertEqual(c.Z, 25, "T loaded Z incorrect.")
        self.assertEqual(c.V, 2.5 / s, "T loaded V incorrect.")

        d = LSection(R(10), R(30) + V(6))
        self.assertEqual(d.Vgain12, 0.75, "Vgain12 incorrect.")
        self.assertEqual(d.Igain12, -1, "Igain12 incorrect.")
        self.assertEqual(d.Z1oc, 40, "Z1oc incorrect.")
        self.assertEqual(d.Z2oc, 30, "Z2oc incorrect.")
        self.assertEqual(d.V1oc, 6 / s, "V1oc incorrect.")
        self.assertEqual(d.V2oc, 6 / s, "V2oc incorrect.")
        self.assertEqual(d.I1sc, 0 / s, "I2sc incorrect.")
        self.assertEqual(d.I2sc, -0.2 / s, "I2sc incorrect.")

        e = d.chain(Series(R(10)))
        self.assertEqual(e.Z1oc, 40, "Z1oc incorrect.")
        self.assertEqual(e.Z2oc, 40, "Z2oc incorrect.")
        self.assertEqual(e.Vgain12, 0.75, "Vgain12 incorrect.")
        self.assertEqual(e.Igain12, -0.75, "Igain12 incorrect.")

    def test_Shunt_parallel(self):
        """Mcircuits: check Shunts in parallel

        """

        a = Shunt(R(10) + V(5))
        b = a.chain(Shunt(R(30)))
        self.assertEqual(b.Z1oc, 7.5, "Z1oc incorrect.")
        self.assertEqual(b.Z2oc, 7.5, "Z2oc incorrect.")
        self.assertEqual(b.V1oc, 3.75 / s, "V1oc incorrect.")
        self.assertEqual(b.V2oc, 3.75 / s, "V2oc incorrect.")

    def test_Series_series(self):
        """Mcircuits: check Series in series

        """

        a = Series(R(10) + V(5))
        b = a.chain(Series(R(30)))
        self.assertEqual(b.Y1sc, 0.025, "Y1sc incorrect.")
        self.assertEqual(b.Y2sc, 0.025, "Y2sc incorrect.")
        self.assertEqual(b.I1sc, -0.125 / s, "I1sc incorrect.")
        self.assertEqual(b.I2sc, 0.125 / s, "I2sc incorrect.")

        c = a.chain(a)

    def test_load(self):
        """Mcircuits: check load

        """

        a = Shunt(R(10) + V(5))
        b = a.load(R(30))

        self.assertEqual(type(b), Thevenin, "type incorrect.")
        self.assertEqual(b.Z, 7.5, "Shunt loaded R incorrect Z.")

    def test_opencircuit(self):
        """Mcircuits: check opencircuit

        """

        a = Shunt(R(10) + V(5))
        b = a.opencircuit()

        self.assertEqual(type(b), Thevenin, "type incorrect.")
        self.assertEqual(b.Z, 10, "incorrect Z.")
        self.assertEqual(b.V, 5 / s, "incorrect V.")

    def test_shortcircuit(self):
        """Mcircuits: check shortcircuit

        """

        a = Series(R(10) + V(5))
        b = a.shortcircuit()

        self.assertEqual(type(b), Norton, "type incorrect.")
        self.assertEqual(b.Z, 10, "incorrect Z.")
        self.assertEqual(b.V, 5 / s, "incorrect V.")

    def test_LSection_models(self):
        """Mcircuits: check LSection models

        """
        a = LSection(R(10) + V(5), R(20))

        self.assertEqual(a.B.B11, 1, "incorrect B11.")
        self.assertEqual(a.B.B12, -10, "incorrect B12.")
        self.assertEqual(a.B.B21, -0.05, "incorrect B21.")
        self.assertEqual(a.B.B22, 1.5, "incorrect B22.")

        b = a.Zmodel

        self.assertEqual(b.B.B11, 1, "incorrect B11.")
        self.assertEqual(b.B.B12, -10, "incorrect B12.")
        self.assertEqual(b.B.B21, -0.05, "incorrect B21.")
        self.assertEqual(b.B.B22, 1.5, "incorrect B22.")

        c = a.Ymodel

        self.assertEqual(c.B.B11, 1, "incorrect B11.")
        self.assertEqual(c.B.B12, -10, "incorrect B12.")
        self.assertEqual(c.B.B21, -0.05, "incorrect B21.")
        self.assertEqual(c.B.B22, 1.5, "incorrect B22.")

        d = a.Hmodel

        self.assertEqual(d.B.B11, 1, "incorrect B11.")
        self.assertEqual(d.B.B12, -10, "incorrect B12.")
        self.assertEqual(d.B.B21, -0.05, "incorrect B21.")
        self.assertEqual(d.B.B22, 1.5, "incorrect B22.")

<commit_msg>Add series and parallel tests<commit_after>from mcircuit import *
import unittest
import sympy as sym
from msignal.mrf import MRF

s = sym.var('s')

class McircuitsTester(unittest.TestCase):
    """Unit tests for mcircuits

    """

    def test_R(self):
        """Mcircuits: check R

        """
        a = R(10)
        self.assertEqual(a.Z, 10, "Z incorrect.")

    def test_L(self):
        """Mcircuits: check L

        """
        a = L(10, 5)
        self.assertEqual(a.Z, 10 * s, "Z incorrect.")
        self.assertEqual(a.V, -10 * 5, "V incorrect.")

    def test_C(self):
        """Mcircuits: check C

        """
        a = C(10, 5)

        self.assertEqual(a.Z, 1 / (10 * s), "Z incorrect.")
        self.assertEqual(a.V, 5 / s, "V incorrect.")

    def test_R_series_R(self):
        """Mcircuits: check R + R

        """
        a = R(10) + R(5)
        b = a.simplify()
        self.assertEqual(b.R, 15, "R incorrect.")
        self.assertEqual(b.Z, 15, "Z incorrect.")
        self.assertEqual(type(b), R, "type incorrect.")

    def test_L_series_L(self):
        """Mcircuits: check L + L

        """
        a = L(10) + L(5)
        b = a.simplify()
        self.assertEqual(b.L, 15, "L incorrect.")
        self.assertEqual(b.Z, 15 * s, "Z incorrect.")
        self.assertEqual(type(b), L, "type incorrect.")

    def test_C_series_C(self):
        """Mcircuits: check C + C

        """
        a = C(10) + C(15)
        b = a.simplify()
        self.assertEqual(b.C, 6, "C incorrect.")
        self.assertEqual(b.Z, 1 / (6 * s), "Z incorrect.")
        self.assertEqual(type(b), C, "type incorrect.")

    def test_V_series_V(self):
        """Mcircuits: check V + V

        """
        a = V(10) + V(5)
        b = a.simplify()
        self.assertEqual(b.v, 15, "V incorrect.")
        self.assertEqual(b.V, 15 / s, "V incorrect.")
        self.assertEqual(type(b), V, "type incorrect.")

    def test_R_series_L(self):
        """Mcircuits: check R + L

        """
        a = R(10) + L(5)
        self.assertEqual(a.Z, 5 * s + 10, "Z incorrect.")
        self.assertEqual(type(a), Thevenin, "type incorrect.")

    def test_R_parallel_R(self):
        """Mcircuits: check R | R

        """
        a = R(10) | R(15)
        b = a.simplify()
        self.assertEqual(b.R, 6, "R incorrect.")
        self.assertEqual(b.Z, 6, "Z incorrect.")
        self.assertEqual(type(b), R, "type incorrect.")

    def test_L_parallel_L(self):
        """Mcircuits: check L | L

        """
        a = L(10) | L(15)
        b = a.simplify()
        self.assertEqual(b.L, 6, "L incorrect.")
        self.assertEqual(b.Z, 6 * s, "Z incorrect.")
        self.assertEqual(type(b), L, "type incorrect.")

    def test_C_parallel_C(self):
        """Mcircuits: check C | C

        """
        a = C(10) | C(15)
        b = a.simplify()
        self.assertEqual(b.C, 25, "C incorrect.")
        self.assertEqual(b.Z, 1 / (25 * s), "Z incorrect.")
        self.assertEqual(type(b), C, "type incorrect.")

    def test_I_parallel_I(self):
        """Mcircuits: check I | I

        """
        a = I(10) | I(5)
        b = a.simplify()
        self.assertEqual(b.i, 15, "I incorrect.")
        self.assertEqual(b.I, 15 / s, "I incorrect.")
        self.assertEqual(type(b), I, "type incorrect.")

    def test_Shunt(self):
        """Mcircuits: check Shunt

        """
        a = Shunt(R(10) + V(5))
        self.assertEqual(a.Z1oc, 10, "Z1oc incorrect.")
        self.assertEqual(a.Z2oc, 10, "Z2oc incorrect.")
        self.assertEqual(a.V2oc, 5 / s, "V2oc incorrect.")
        print(a.I2sc)
        self.assertEqual(a.I2sc, 0.5 / s, "I2sc incorrect.")
        self.assertEqual(a.V1oc, 5 / s, "V1oc incorrect.")
        self.assertEqual(a.I1sc, 0.5 / s, "I1sc incorrect.")

        b = a.chain(Shunt(R(30)))
        self.assertEqual(b.Z1oc, 7.5, "Z1oc incorrect.")
        self.assertEqual(b.Z2oc, 7.5, "Z2oc incorrect.")
        self.assertEqual(b.V2oc, 3.75 / s, "V2oc incorrect.")
        self.assertEqual(b.I2sc, 0.5 / s, "I2sc incorrect.")
        self.assertEqual(b.V1oc, 3.75 / s, "V1oc incorrect.")
        self.assertEqual(b.I1sc, 0.5 / s, "I1sc incorrect.")

        c = a.chain(Series(R(30)))
        self.assertEqual(c.Z1oc, 10, "Z1oc incorrect.")
        self.assertEqual(c.Z2oc, 40, "Z2oc incorrect.")
        self.assertEqual(c.V1oc, 5 / s, "V1oc incorrect.")
        self.assertEqual(c.V2oc, 5 / s, "V2oc incorrect.")
        self.assertEqual(c.I1sc, -0.5 / s, "I1sc incorrect.")
        self.assertEqual(c.I2sc, 0 / s, "I2sc incorrect.")

        d = Shunt(R(10)).chain(Series(R(30)))

    def test_Series(self):
        """Mcircuits: check Series

        """
        a = Series(R(10) + V(5))
        self.assertEqual(a.Y1sc, 0.1, "Y1sc incorrect.")
        self.assertEqual(a.Y2sc, 0.1, "Y2sc incorrect.")
        print(a.V1oc)
        self.assertEqual(a.V1oc, 5 / s, "V1oc incorrect.")
        self.assertEqual(a.V2oc, 5 / s, "V2oc incorrect.")
        self.assertEqual(a.I1sc, 0.5 / s, "I1sc incorrect.")
        self.assertEqual(a.I2sc, -0.5 / s, "I2sc incorrect.")

    def test_LSection(self):
        """Mcircuits: check LSection

        """
        a = LSection(R(10), R(30))

        self.assertEqual(a.Vgain12, 0.75, "Vgain12 incorrect.")
        self.assertEqual(a.Igain12, -1, "Igain12 incorrect.")
        self.assertEqual(a.Z1oc, 40, "Z1oc incorrect.")
        self.assertEqual(a.Z2oc, 30, "Z2oc incorrect.")
        self.assertEqual(a.Z1sc, 10, "Z1sc incorrect.")
        self.assertEqual(a.Z2sc, 7.5, "Z2sc incorrect.")
        self.assertEqual(a.Vgain(1, 2), 0.75, "Vgain 1->2 incorrect.")
        self.assertEqual(a.Vgain(2, 1), 1, "Vgain 2->1 incorrect.")
        self.assertEqual(a.Igain(1, 2), -1, "Igain 1->2 incorrect.")
        self.assertEqual(a.Igain(2, 1), -0.75, "Igain 2->1 incorrect.")
        self.assertEqual(a.Vresponse(V(1), 1, 2), 0.75 / s, "Vresponse 1->2 incorrect.")
        self.assertEqual(a.Vresponse(V(1), 2, 1), 1 / s, "Vresponse 2->1 incorrect.")
        self.assertEqual(a.Iresponse(I(1), 1, 2), -1 / s, "Iresponse 1->2 incorrect.")
        self.assertEqual(a.Iresponse(I(1), 2, 1), -0.75 / s, "Iresponse 2->1 incorrect.")

        b = a.load(R(30))
        self.assertEqual(type(b), Thevenin, "type incorrect.")
        self.assertEqual(b.Z, 25, "R loaded Z incorrect.")
        self.assertEqual(b.V, 0, "R loaded V incorrect.")

        c = a.load(R(30) + V(5))
        self.assertEqual(type(b), Thevenin, "type incorrect.")
        self.assertEqual(c.Z, 25, "T loaded Z incorrect.")
        self.assertEqual(c.V, 2.5 / s, "T loaded V incorrect.")

        d = LSection(R(10), R(30) + V(6))
        self.assertEqual(d.Vgain12, 0.75, "Vgain12 incorrect.")
        self.assertEqual(d.Igain12, -1, "Igain12 incorrect.")
        self.assertEqual(d.Z1oc, 40, "Z1oc incorrect.")
        self.assertEqual(d.Z2oc, 30, "Z2oc incorrect.")
        self.assertEqual(d.V1oc, 6 / s, "V1oc incorrect.")
        self.assertEqual(d.V2oc, 6 / s, "V2oc incorrect.")
        self.assertEqual(d.I1sc, 0 / s, "I2sc incorrect.")
        self.assertEqual(d.I2sc, -0.2 / s, "I2sc incorrect.")

        e = d.chain(Series(R(10)))
        self.assertEqual(e.Z1oc, 40, "Z1oc incorrect.")
        self.assertEqual(e.Z2oc, 40, "Z2oc incorrect.")
        self.assertEqual(e.Vgain12, 0.75, "Vgain12 incorrect.")
        self.assertEqual(e.Igain12, -0.75, "Igain12 incorrect.")

    def test_Shunt_parallel(self):
        """Mcircuits: check Shunts in parallel

        """

        a = Shunt(R(10) + V(5))
        b = a.chain(Shunt(R(30)))
        self.assertEqual(b.Z1oc, 7.5, "Z1oc incorrect.")
        self.assertEqual(b.Z2oc, 7.5, "Z2oc incorrect.")
        self.assertEqual(b.V1oc, 3.75 / s, "V1oc incorrect.")
        self.assertEqual(b.V2oc, 3.75 / s, "V2oc incorrect.")

    def test_Series_series(self):
        """Mcircuits: check Series in series

        """

        a = Series(R(10) + V(5))
        b = a.chain(Series(R(30)))
        self.assertEqual(b.Y1sc, 0.025, "Y1sc incorrect.")
        self.assertEqual(b.Y2sc, 0.025, "Y2sc incorrect.")
        self.assertEqual(b.I1sc, -0.125 / s, "I1sc incorrect.")
        self.assertEqual(b.I2sc, 0.125 / s, "I2sc incorrect.")

        c = a.chain(a)

    def test_load(self):
        """Mcircuits: check load

        """

        a = Shunt(R(10) + V(5))
        b = a.load(R(30))

        self.assertEqual(type(b), Thevenin, "type incorrect.")
        self.assertEqual(b.Z, 7.5, "Shunt loaded R incorrect Z.")

    def test_open_circuit(self):
        """Mcircuits: check open_circuit

        """

        a = Shunt(R(10) + V(5))
        b = a.open_circuit()

        self.assertEqual(type(b), Thevenin, "type incorrect.")
        self.assertEqual(b.Z, 10, "incorrect Z.")
        self.assertEqual(b.V, 5 / s, "incorrect V.")

    def test_short_circuit(self):
        """Mcircuits: check short_circuit

        """

        a = Series(R(10) + V(5))
        b = a.short_circuit()

        self.assertEqual(type(b), Norton, "type incorrect.")
        self.assertEqual(b.Z, 10, "incorrect Z.")
        self.assertEqual(b.V, 5 / s, "incorrect V.")

    def test_LSection_models(self):
        """Mcircuits: check LSection models

        """
        a = LSection(R(10) + V(5), R(20))

        self.assertEqual(a.B.B11, 1, "incorrect B11.")
        self.assertEqual(a.B.B12, -10, "incorrect B12.")
        self.assertEqual(a.B.B21, -0.05, "incorrect B21.")
        self.assertEqual(a.B.B22, 1.5, "incorrect B22.")

        b = a.Zmodel

        self.assertEqual(b.B.B11, 1, "incorrect B11.")
        self.assertEqual(b.B.B12, -10, "incorrect B12.")
        self.assertEqual(b.B.B21, -0.05, "incorrect B21.")
        self.assertEqual(b.B.B22, 1.5, "incorrect B22.")

        c = a.Ymodel

        self.assertEqual(c.B.B11, 1, "incorrect B11.")
        self.assertEqual(c.B.B12, -10, "incorrect B12.")
        self.assertEqual(c.B.B21, -0.05, "incorrect B21.")
        self.assertEqual(c.B.B22, 1.5, "incorrect B22.")

        d = a.Hmodel

        self.assertEqual(d.B.B11, 1, "incorrect B11.")
        self.assertEqual(d.B.B12, -10, "incorrect B12.")
        self.assertEqual(d.B.B21, -0.05, "incorrect B21.")
        self.assertEqual(d.B.B22, 1.5, "incorrect B22.")

<|endoftext|>


<commit_before>"""
Wrap the tkinter function for this project
"""
import logging
import sys
import time
import tkinter
from inspect import currentframe, getframeinfo
from os.path import abspath
from tkinter import ttk

import cv2
from PIL import Image, ImageTk

sys.path.append('../..')
from src.image.imnp import ImageNP
from src.support.tkconvert import TkConverter
from src.view.tkfonts import TkFonts
from src.view.tkframe import TkFrame, TkLabelFrame
from src.view.ttkstyle import TTKStyle, init_css

LOGGER = logging.getLogger(__name__)
STATE = ['view', 'erase', 'mirror', 'seperate']

class MothViewerTemplate(object):

    def __init__(self):
        """
        Assume all image paths in self.image_queue are unique
        Argument:
            @current_image_path
            @image_queue        a queue of image path
            @image_panel        the image in panel
            @image_template     the template image for detection
            @state              the state to determine the operation
        """
        super().__init__()
        self.current_image_path = None
        self.image_queue = None
        self.image_panel = None
        self.image_path_template = None
        self.state = None
        self._font = TkFonts()

        # init windows, widget and layout
        self._init_window()
        self._init_style()
        self._init_frame()
        self._init_widget_head()
        self._init_widget_body()
        self._init_widget_footer()

    # set grid all column configure
    def _set_all_grid_columnconfigure(self, widget, *cols):
        for col in cols:
            widget.grid_columnconfigure(col, weight=1)

    # set grid all row comfigure
    def _set_all_grid_rowconfigure(self, widget, *rows):
        for row in rows:
            widget.grid_rowconfigure(row, weight=1)

    # init root window
    def _init_window(self):
        """Windows init - root"""
        self.root = tkinter.Tk()
        self.root.wm_title(time.ctime())
        self.root.grid_rowconfigure(0, weight=1)
        self.root.grid_columnconfigure(0, weight=1)
        self.root.attributes('-zoomed', True)

    # init ttk widget style
    def _init_style(self):
        init_css()

    # init tk frame and grid layout
    def _init_frame(self):
        """root"""
        self.frame_root = TkFrame(self.root, bg='white')
        self.frame_root.grid(row=0, column=0)
        self._set_all_grid_columnconfigure(self.frame_root, 0)
        self._set_all_grid_rowconfigure(self.frame_root, 0, 1, 2)

        """root.header"""
        self.frame_nav = TkFrame(self.frame_root, bg='orange')
        self.frame_nav.grid(row=0, column=0, sticky='news')

        """root.body"""
        self.frame_body = TkFrame(self.frame_root, bg='black')
        self.frame_body.grid(row=1, column=0, sticky='news')
        self._set_all_grid_columnconfigure(self.frame_body, 0, 1)
        self._set_all_grid_rowconfigure(self.frame_body, 0)

        """root.body.frame_panel"""
        self.frame_panel = TkFrame(self.frame_body, bg='light pink')
        self.frame_panel.grid(row=0, column=0, sticky='news')
        self._set_all_grid_columnconfigure(self.frame_panel, 0)
        self._set_all_grid_rowconfigure(self.frame_panel, 0, 1)

        """root.body.frame_display"""
        self.frame_display = TkFrame(self.frame_body, bg='royal blue')
        self.frame_display.grid(row=0, column=1, sticky='news')
        self._set_all_grid_columnconfigure(self.frame_display, 0)
        self._set_all_grid_rowconfigure(self.frame_display, 0, 1)

        """root.footer"""
        self.frame_footer = TkFrame(self.frame_root, bg='khaki1')
        self.frame_footer.grid(row=2, column=0, sticky='news')
        self._set_all_grid_columnconfigure(self.frame_footer, 0)
        self._set_all_grid_rowconfigure(self.frame_footer, 0, 1)

        """root.footer.input_option"""
        self.frame_input_option = TkLabelFrame(self.frame_footer, text=u'Input 調整參數', font=self._font.h4(), bg='gray86')
        self.frame_input_option.grid(row=0, column=0, sticky='news')
        self._set_all_grid_columnconfigure(self.frame_input_option, 0)
        self._set_all_grid_rowconfigure(self.frame_input_option, 0, 1)

        """root.footer.input_option.detect_options"""
        self.frame_detect_options = TkFrame(self.frame_input_option, padx=0, pady=0)
        self.frame_detect_options.grid(row=0, column=1, sticky='news')

        """root.footer.output_option"""
        self.frame_output_option = TkLabelFrame(self.frame_footer, text=u'Output 調整參數', font=self._font.h4(), bg='gray86')
        self.frame_output_option.grid(row=1, column=0, sticky='news')
        self._set_all_grid_columnconfigure(self.frame_output_option, 0)
        self._set_all_grid_rowconfigure(self.frame_output_option, 0)

    # init header widget
    def _init_widget_head(self):
        """State"""
        self.label_state = ttk.Label(self.frame_nav, text='', style='H1.TLabel')
        self.label_state.grid(row=0, column=0, sticky='w')
        self._sync_state()

    # init body widget
    def _init_widget_body(self):
        """Panel/Display label"""
        self.label_panel = ttk.Label(self.frame_panel, text='Input Panel', style='H2.TLabel')
        self.label_display = ttk.Label(self.frame_display, text='Display', style='H2.TLabel')
        self.label_panel.grid(row=0, column=0)
        self.label_display.grid(row=0, column=0)

        """Panel/Display image"""
        # default output
        self._image_w, self._image_h = 800, 533
        self.photo_panel = ImageNP.generate_checkboard((self._image_h, self._image_w), block_size=10)
        self.photo_panel = TkConverter.ndarray_to_photo(self.photo_panel)
        self.photo_display = self.photo_panel

        self.label_panel_image = ttk.Label(self.frame_panel, image=self.photo_panel)
        self.label_panel_image.image = self.photo_panel
        self.label_panel_image.grid(row=2, column=0)
        self.label_display_image = ttk.Label(self.frame_display, image=self.photo_display)
        self.label_display_image.grid(row=2, column=0)

    # init footer widget
    def _init_widget_footer(self):
        """Input option"""
        # detector
        self.label_detector = ttk.Label(self.frame_input_option, text=u'偵測設定: ', style='H5.TLabel')
        self.label_detector.grid(row=0, column=0, sticky='w')
        self.val_manual_detect = tkinter.BooleanVar()
        self.val_template_detect = tkinter.BooleanVar()
        self.val_manual_detect.set(False)
        self.val_template_detect.set(False)
        self.checkbtn_manual_detect = ttk.Checkbutton(self.frame_detect_options,
                                                      text=u'手動清除',
                                                      style='H5.TCheckbutton',
                                                      variable=self.val_manual_detect)
        self.checkbtn_template_detect = ttk.Checkbutton(self.frame_detect_options,
                                                        text=u'自動清除',
                                                        style='H5.TCheckbutton',
                                                        variable=self.val_template_detect)
        self.checkbtn_template_detect.configure(state='disabled')
        self.checkbtn_manual_detect.grid(row=0, column=0, sticky='w')
        self.checkbtn_template_detect.grid(row=0, column=1, sticky='w')

        # scale bar
        self.label_gamma = ttk.Label(self.frame_input_option, text=u'圖片對比: ', style='H5.TLabel')
        self.val_gamma = tkinter.DoubleVar()
        self.val_gamma.set(1.0)
        self.scale_gamma = ttk.Scale(self.frame_input_option,
                                     orient=tkinter.HORIZONTAL,
                                     length=self._image_w*2,
                                     from_=0.01, to=2.5,
                                     variable=self.val_gamma,
                                     style='White.Horizontal.TScale')
        self.label_gamma.grid(row=1, column=0, sticky='news')
        self.scale_gamma.grid(row=1, column=1, sticky='news')

        """Output option"""
        # scale bar
        self.label_threshold = ttk.Label(self.frame_output_option, text=u'門檻值: ', style='H5.TLabel')
        self.val_threshold = tkinter.IntVar()
        self.val_threshold.set(250)
        self.scale_threshold = ttk.Scale(self.frame_output_option,
                                         orient=tkinter.HORIZONTAL,
                                         length=self._image_w*2,
                                         from_=1, to=255,
                                         variable=self.val_threshold,
                                         style='White.Horizontal.TScale')
        self.label_threshold.grid(row=0, column=0, sticky='news')
        self.scale_threshold.grid(row=0, column=1, sticky='news')

    # update detector option
    def _enable_detector(self):
        if self.image_path_template is not None:
            self.image_template = cv2.imread(self.image_path_template)
            self.checkbtn_template_detect.configure(state='normal')
            self.val_template_detect.set(True)
        else:
            LOGGER.warning('No template image given')

    # read a new image and update to panel
    def _update_image(self, image_path=None):
        if image_path is not None:
            try:
                self.photo_panel = tkinter.PhotoImage(file=image_path)
                self._image_w, self._image_h = self.photo_panel.width(), self.photo_panel.height()
            except tkinter.TclError as loaderr:
                # log level should be warning in this block
                LOGGER.debug(loaderr)
                self.image_panel = cv2.imread(image_path)
                self._image_h, self._image_w, _ = self.image_panel.shape
                self.photo_panel = TkConverter.cv2_to_photo(self.image_panel)
            except Exception as e:
                LOGGER.exception(e)
            self.current_image_path = image_path
        else:
            # LOGGER.warning('No image_path')
            self.photo_panel = TkConverter.cv2_to_photo(self.image_panel)

    # render the lastest panel image
    def _sync_image(self):
        self.root.wm_title(self.current_image_path)
        self.label_panel_image.config(image=self.photo_panel)
        self.label_panel_image.after(100, self._sync_image)

    # render the lastest state
    def _sync_state(self):
        if self.state not in STATE:
            self.label_state.configure(text=u'現在模式: {}'.format(u'無'))
        elif self.state == 'view':
            self.label_state.configure(text=u'現在模式: {}'.format(u'瀏覽'))
        elif self.state == 'erase':
            self.label_state.configure(text=u'現在模式: {}'.format(u'手動消除'))
        elif self.state == 'mirror':
            self.label_state.configure(text=u'現在模式: {}'.format(u'鏡像'))
        elif self.state == 'seperate':
            self.label_state.configure(text=u'現在模式: {}'.format(u'切割'))

        self.label_state.after(100, self._sync_state)

    # input template image
    def input_template(self, template_path):
        self.image_path_template = template_path
        self._enable_detector()

    # input all image path to queue
    def input_image(self, *image_paths):
        self.image_queue = image_paths
        self._update_image(self.image_queue[0])

    # inherit tkinter mainloop
    def mainloop(self):
        self._sync_image()
        self.root.mainloop()

if __name__ == '__main__':
    """testing"""
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s %(filename)12s:L%(lineno)3s [%(levelname)8s] %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S',
        stream=sys.stdout
        )
    _FILE = abspath(getframeinfo(currentframe()).filename)
    TEMPLATE_IMG = abspath('../../image/10mm.png')
    SAMPLE_IMG = abspath('../../image/sample/0.jpg')

    viewer = MothViewerTemplate()
    viewer.input_template(TEMPLATE_IMG)
    viewer.input_image(SAMPLE_IMG)
    viewer.mainloop()
<commit_msg>modified: degrade detector from action to template but None and add init_state<commit_after>"""
Wrap the tkinter function for this project
"""
import logging
import sys
import time
import tkinter
from inspect import currentframe, getframeinfo
from os.path import abspath
from tkinter import ttk

import cv2
from PIL import Image, ImageTk

sys.path.append('../..')
from src.image.imnp import ImageNP
from src.support.tkconvert import TkConverter
from src.view.tkfonts import TkFonts
from src.view.tkframe import TkFrame, TkLabelFrame
from src.view.ttkstyle import TTKStyle, init_css

LOGGER = logging.getLogger(__name__)
STATE = ['view', 'erase', 'mirror', 'seperate']

class MothViewerTemplate(object):

    def __init__(self):
        """
        Assume all image paths in self.image_queue are unique
        Argument:
            @current_image_path
            @image_queue        a queue of image path
            @image_panel        the image in panel
            @image_template     the template image for detection
            @state              the state to determine the operation
        """
        super().__init__()
        self.current_image_path = None
        self.image_queue = None
        self.image_panel = None
        self.image_path_template = None
        self.detector = None
        self.state = None
        self._font = TkFonts()

        # init windows, widget and layout
        self._init_window()
        self._init_style()
        self._init_frame()
        self._init_widget_head()
        self._init_widget_body()
        self._init_widget_footer()

    # set grid all column configure
    def _set_all_grid_columnconfigure(self, widget, *cols):
        for col in cols:
            widget.grid_columnconfigure(col, weight=1)

    # set grid all row comfigure
    def _set_all_grid_rowconfigure(self, widget, *rows):
        for row in rows:
            widget.grid_rowconfigure(row, weight=1)

    # init all state when image changed
    def _init_state(self):
        self.detector = None
        self.state = 'view'

    # init root window
    def _init_window(self):
        """Windows init - root"""
        self.root = tkinter.Tk()
        self.root.wm_title(time.ctime())
        self.root.grid_rowconfigure(0, weight=1)
        self.root.grid_columnconfigure(0, weight=1)
        self.root.attributes('-zoomed', True)

    # init ttk widget style
    def _init_style(self):
        init_css()

    # init tk frame and grid layout
    def _init_frame(self):
        """root"""
        self.frame_root = TkFrame(self.root, bg='white')
        self.frame_root.grid(row=0, column=0)
        self._set_all_grid_columnconfigure(self.frame_root, 0)
        self._set_all_grid_rowconfigure(self.frame_root, 0, 1, 2)

        """root.header"""
        self.frame_nav = TkFrame(self.frame_root, bg='orange')
        self.frame_nav.grid(row=0, column=0, sticky='news')

        """root.body"""
        self.frame_body = TkFrame(self.frame_root, bg='black')
        self.frame_body.grid(row=1, column=0, sticky='news')
        self._set_all_grid_columnconfigure(self.frame_body, 0, 1)
        self._set_all_grid_rowconfigure(self.frame_body, 0)

        """root.body.frame_panel"""
        self.frame_panel = TkFrame(self.frame_body, bg='light pink')
        self.frame_panel.grid(row=0, column=0, sticky='news')
        self._set_all_grid_columnconfigure(self.frame_panel, 0)
        self._set_all_grid_rowconfigure(self.frame_panel, 0, 1)

        """root.body.frame_display"""
        self.frame_display = TkFrame(self.frame_body, bg='royal blue')
        self.frame_display.grid(row=0, column=1, sticky='news')
        self._set_all_grid_columnconfigure(self.frame_display, 0)
        self._set_all_grid_rowconfigure(self.frame_display, 0, 1)

        """root.footer"""
        self.frame_footer = TkFrame(self.frame_root, bg='khaki1')
        self.frame_footer.grid(row=2, column=0, sticky='news')
        self._set_all_grid_columnconfigure(self.frame_footer, 0)
        self._set_all_grid_rowconfigure(self.frame_footer, 0, 1)

        """root.footer.input_option"""
        self.frame_input_option = TkLabelFrame(self.frame_footer, text=u'Input 調整參數', font=self._font.h4(), bg='gray86')
        self.frame_input_option.grid(row=0, column=0, sticky='news')
        self._set_all_grid_columnconfigure(self.frame_input_option, 0)
        self._set_all_grid_rowconfigure(self.frame_input_option, 0, 1)

        """root.footer.input_option.detect_options"""
        self.frame_detect_options = TkFrame(self.frame_input_option, padx=0, pady=0)
        self.frame_detect_options.grid(row=0, column=1, sticky='news')

        """root.footer.output_option"""
        self.frame_output_option = TkLabelFrame(self.frame_footer, text=u'Output 調整參數', font=self._font.h4(), bg='gray86')
        self.frame_output_option.grid(row=1, column=0, sticky='news')
        self._set_all_grid_columnconfigure(self.frame_output_option, 0)
        self._set_all_grid_rowconfigure(self.frame_output_option, 0)

    # init header widget
    def _init_widget_head(self):
        """State"""
        self.label_state = ttk.Label(self.frame_nav, text='', style='H1.TLabel')
        self.label_state.grid(row=0, column=0, sticky='w')
        self._sync_state()

    # init body widget
    def _init_widget_body(self):
        """Panel/Display label"""
        self.label_panel = ttk.Label(self.frame_panel, text='Input Panel', style='H2.TLabel')
        self.label_display = ttk.Label(self.frame_display, text='Display', style='H2.TLabel')
        self.label_panel.grid(row=0, column=0)
        self.label_display.grid(row=0, column=0)

        """Panel/Display image"""
        # default output
        self._image_w, self._image_h = 800, 533
        self.photo_panel = ImageNP.generate_checkboard((self._image_h, self._image_w), block_size=10)
        self.photo_panel = TkConverter.ndarray_to_photo(self.photo_panel)
        self.photo_display = self.photo_panel

        self.label_panel_image = ttk.Label(self.frame_panel, image=self.photo_panel)
        self.label_panel_image.image = self.photo_panel
        self.label_panel_image.grid(row=2, column=0)
        self.label_display_image = ttk.Label(self.frame_display, image=self.photo_display)
        self.label_display_image.grid(row=2, column=0)

    # init footer widget
    def _init_widget_footer(self):
        """Input option"""
        # detector
        self.label_detector = ttk.Label(self.frame_input_option, text=u'偵測設定: ', style='H5.TLabel')
        self.label_detector.grid(row=0, column=0, sticky='w')
        self.val_manual_detect = tkinter.BooleanVar()
        self.val_template_detect = tkinter.BooleanVar()
        self.val_manual_detect.set(False)
        self.val_template_detect.set(False)
        self.checkbtn_manual_detect = ttk.Checkbutton(self.frame_detect_options,
                                                      text=u'手動清除',
                                                      style='H5.TCheckbutton',
                                                      variable=self.val_manual_detect)
        self.checkbtn_template_detect = ttk.Checkbutton(self.frame_detect_options,
                                                        text=u'自動清除',
                                                        style='H5.TCheckbutton',
                                                        variable=self.val_template_detect)
        self.checkbtn_template_detect.configure(state='disabled')
        self.checkbtn_manual_detect.grid(row=0, column=0, sticky='w')
        self.checkbtn_template_detect.grid(row=0, column=1, sticky='w')

        # scale bar
        self.label_gamma = ttk.Label(self.frame_input_option, text=u'圖片對比: ', style='H5.TLabel')
        self.val_gamma = tkinter.DoubleVar()
        self.val_gamma.set(1.0)
        self.scale_gamma = ttk.Scale(self.frame_input_option,
                                     orient=tkinter.HORIZONTAL,
                                     length=self._image_w*2,
                                     from_=0.01, to=2.5,
                                     variable=self.val_gamma,
                                     style='White.Horizontal.TScale')
        self.label_gamma.grid(row=1, column=0, sticky='news')
        self.scale_gamma.grid(row=1, column=1, sticky='news')

        """Output option"""
        # scale bar
        self.label_threshold = ttk.Label(self.frame_output_option, text=u'門檻值: ', style='H5.TLabel')
        self.val_threshold = tkinter.IntVar()
        self.val_threshold.set(250)
        self.scale_threshold = ttk.Scale(self.frame_output_option,
                                         orient=tkinter.HORIZONTAL,
                                         length=self._image_w*2,
                                         from_=1, to=255,
                                         variable=self.val_threshold,
                                         style='White.Horizontal.TScale')
        self.label_threshold.grid(row=0, column=0, sticky='news')
        self.scale_threshold.grid(row=0, column=1, sticky='news')

    # update detector option
    def _enable_detector(self):
        if self.image_path_template is not None:
            self.image_template = cv2.imread(self.image_path_template)
            self.checkbtn_template_detect.configure(state='normal')
            self.val_template_detect.set(True)
        else:
            LOGGER.warning('No template image given')

    # read a new image and update to panel
    def _update_image(self, image_path=None):
        if image_path is not None:
            try:
                self.photo_panel = tkinter.PhotoImage(file=image_path)
                self._image_w, self._image_h = self.photo_panel.width(), self.photo_panel.height()
            except tkinter.TclError as loaderr:
                # log level should be warning in this block
                LOGGER.debug(loaderr)
                self.image_panel = cv2.imread(image_path)
                self._image_h, self._image_w, _ = self.image_panel.shape
                self.photo_panel = TkConverter.cv2_to_photo(self.image_panel)
            except Exception as e:
                LOGGER.exception(e)
            self.current_image_path = image_path
        else:
            # LOGGER.warning('No image_path')
            self.photo_panel = TkConverter.cv2_to_photo(self.image_panel)

    # render the lastest panel image
    def _sync_image(self):
        self.root.wm_title(self.current_image_path)
        self.label_panel_image.config(image=self.photo_panel)
        self.label_panel_image.after(100, self._sync_image)

    # render the lastest state
    def _sync_state(self):
        if self.state not in STATE:
            self.label_state.configure(text=u'現在模式: {}'.format(u'無'))
        elif self.state == 'view':
            self.label_state.configure(text=u'現在模式: {}'.format(u'瀏覽'))
        elif self.state == 'erase':
            self.label_state.configure(text=u'現在模式: {}'.format(u'手動消除'))
        elif self.state == 'mirror':
            self.label_state.configure(text=u'現在模式: {}'.format(u'鏡像'))
        elif self.state == 'seperate':
            self.label_state.configure(text=u'現在模式: {}'.format(u'切割'))

        self.label_state.after(100, self._sync_state)

    # input template image
    def input_template(self, template_path):
        self.image_path_template = template_path
        self._enable_detector()

    # input all image path to queue
    def input_image(self, *image_paths):
        self.image_queue = image_paths
        self._update_image(self.image_queue[0])

    # inherit tkinter mainloop
    def mainloop(self):
        self._sync_image()
        self.root.mainloop()

if __name__ == '__main__':
    """testing"""
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s %(filename)12s:L%(lineno)3s [%(levelname)8s] %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S',
        stream=sys.stdout
        )
    _FILE = abspath(getframeinfo(currentframe()).filename)
    TEMPLATE_IMG = abspath('../../image/10mm.png')
    SAMPLE_IMG = abspath('../../image/sample/0.jpg')

    viewer = MothViewerTemplate()
    viewer.input_template(TEMPLATE_IMG)
    viewer.input_image(SAMPLE_IMG)
    viewer.mainloop()
<|endoftext|>


<commit_before># Functions which are common and require SciPy Base and Level 1 SciPy
# (stats, special, linalg)

# Needs eigenvalues
import Numeric
import types, sys
from scipy import special, stats, linalg
from scipy_base import exp, amin, amax, ravel, asarray, cast, arange, \
     ones, NewAxis, transpose, hstack, product, array, typename, where, \
     zeros, extract, insert, pi, sqrt
import scipy_base.fastumath

__all__ = ['factorial','factorial2','comb','rand','randn','who',
           'lena','central_diff_weights', 'derivative']

def factorial(n,exact=0):
    """n! = special.gamma(n+1)

    If exact==0, then floating point precision is used, otherwise
    exact long integer is computed.

    Notes:
      - Array argument accepted only for exact=0 case.
      - If n<0, the return value is 0.
    """
    if exact:
        if n < 0:
            return 0L
        n = long(n)
        val = 1L
        k = 1L
        while (k < n+1L):
            val = val*k
            k += 1
        return val
    else:
        n = asarray(n)
        sv = special.errprint(0)
        vals = special.gamma(n+1)
        sv = special.errprint(sv)
        return where(n>=0,vals,0)

def factorial2(n,exact=0):
    """n!! = special.gamma(n/2+1)*2**((m+1)/2)/sqrt(pi)  n odd
           = 2**(n) * n!                                 n even

    If exact==0, then floating point precision is used, otherwise
    exact long integer is computed.

    Notes:
      - Array argument accepted only for exact=0 case.
      - If n<0, the return value is 0.
    """
    if exact:
        if n < -1:
            return 0L
        if n <= 0:
            return 1L
        n = long(n)
        val = 1L
        k = n
        while (k > 0):
            val = val*k
            k -= 2
        return val
    else:
        n = asarray(n)
        vals = zeros(n.shape,'d')
        cond1 = (n % 2) & (n >= -1)
        cond2 = (1-(n % 2)) & (n >= -1)
        oddn = extract(n,cond1)
        evenn = extract(n,cond2)
        nd2o = oddn / 2.0
        nd2e = evenn / 2.0
        insert(vals,cond1,special.gamma(nd2o+1)/sqrt(pi)*pow(2.0,nd2o+0.5))
        insert(vals,cond2,special.gamma(nd2e+1) * pow(2.0,nd2e))
        return vals

def comb(N,k,exact=0):
    """Combinations of N things taken k at a time.

    If exact==0, then floating point precision is used, otherwise
    exact long integer is computed.

    Notes:
      - Array arguments accepted only for exact=0 case.
      - If k > N, N < 0, or k < 0, then a 0 is returned.
    """
    if exact:
        if (k > N) or (N < 0) or (k < 0):
            return 0L
        N,k = map(long,(N,k))
        top = N
        val = 1L
        while (top > (N-k)):
            val *= top
            top -= 1
        n = 1L
        while (n < k+1L):
            val /= n
            n += 1
        return val
    else:
        k,N = asarray(k), asarray(N)
        lgam = special.gammaln
        cond = (k <= N) & (N >= 0) & (k >= 0)
        sv = special.errprint(0)
        vals = exp(lgam(N+1) - lgam(N-k+1) - lgam(k+1))
        sv = special.errprint(sv)
        return where(cond, vals, 0.0)

def central_diff_weights(Np,ndiv=1):
    """Return weights for an Np-point central derivative of order ndiv
       assuming equally-spaced function points.

       If weights are in the vector w, then
       derivative is w[0] * f(x-ho*dx) + ... + w[-1] * f(x+h0*dx)

       Can be inaccurate for large number of points.
    """
    assert (Np >= ndiv+1), "Number of points must be at least the derivative order + 1."
    assert (Np % 2 == 1), "Odd-number of points only."
    ho = Np >> 1
    x = arange(-ho,ho+1.0)
    x = x[:,NewAxis]
    X = x**0.0
    for k in range(1,Np):
        X = hstack([X,x**k])
    w = product(arange(1,ndiv+1))*linalg.inv(X)[ndiv]
    return w

def derivative(func,x0,dx=1.0,n=1,args=(),order=3):
    """Given a function, use an N-point central differenece
       formula with spacing dx to compute the nth derivative at
       x0, where N is the value of order and must be odd.

       Warning: Decreasing the step size too small can result in
       round-off error.
    """
    assert (order >= n+1), "Number of points must be at least the derivative order + 1."
    assert (order % 2 == 1), "Odd number of points only."
    # pre-computed for n=1 and 2 and low-order for speed.
    if n==1:
        if order == 3:
            weights = array([-1,0,1])/2.0
        elif order == 5:
            weights = array([1,-8,0,8,-1])/12.0
        elif order == 7:
            weights = array([-1,9,-45,0,45,-9,1])/60.0
        elif order == 9:
            weights = array([3,-32,168,-672,0,672,-168,32,-3])/840.0
        else:
            weights = central_diff_weights(order,1)
    elif n==2:
        if order == 3:
            weights = array([1,-2.0,1])
        elif order == 5:
            weights = array([-1,16,-30,16,-1])/12.0
        elif order == 7:
            weights = array([2,-27,270,-490,270,-27,2])/180.0
        elif order == 9:
            weights = array([-9,128,-1008,8064,-14350,8064,-1008,128,-9])/5040.0
        else:
            weights = central_diff_weights(order,2)
    else:
        weights = central_diff_weights(order, n)
    val = 0.0
    ho = order >> 1
    for k in range(order):
        val += weights[k]*func(x0+(k-ho)*dx,*args)
    return val / product((dx,)*n)

def rand(*args):
    """rand(d1,...,dn) returns a matrix of the given dimensions
    which is initialized to random numbers from a uniform distribution
    in the range [0,1).
    """
    return stats.random(args)

def randn(*args):
    """u = randn(d0,d1,...,dn) returns zero-mean, unit-variance Gaussian
    random numbers in an array of size (d0,d1,...,dn)."""
    return stats.stnorm(size=args)

def lena():
    import cPickle, os
    d,junk = os.path.split(os.path.abspath(scipy.__file__))
    fname = os.path.join(d,'plt','lena.dat')
    f = open(fname,'rb')
    lena = scipy.array(cPickle.load(f))
    f.close()
    return lena

#-----------------------------------------------------------------------------
# Matlab like functions for output and information on the variables used.
#-----------------------------------------------------------------------------

def who(vardict=None):
    """Print the Numeric arrays in the given dictionary (or globals() if None).
    """
    if vardict is None:
        print "Pass in a dictionary:  who(globals())"
        return
    sta = []
    cache = {}
    for name in vardict.keys():
        if isinstance(vardict[name],Numeric.ArrayType):
            var = vardict[name]
            idv = id(var)
            if idv in cache.keys():
                namestr = name + " (%s)" % cache[idv]
                original=0
            else:
                cache[idv] = name
                namestr = name
                original=1
            shapestr = " x ".join(map(str, var.shape))
            bytestr = str(var.itemsize()*Numeric.product(var.shape))
            sta.append([namestr, shapestr, bytestr, typename(var.typecode()), original])

    maxname = 0
    maxshape = 0
    maxbyte = 0
    totalbytes = 0
    for k in range(len(sta)):
        val = sta[k]
        if maxname < len(val[0]):
            maxname = len(val[0])
        if maxshape < len(val[1]):
            maxshape = len(val[1])
        if maxbyte < len(val[2]):
            maxbyte = len(val[2])
        if val[4]:
            totalbytes += int(val[2])

    max = Numeric.maximum
    if len(sta) > 0:
        sp1 = max(10,maxname)
        sp2 = max(10,maxshape)
        sp3 = max(10,maxbyte)
        prval = "Name %s Shape %s Bytes %s Type" % (sp1*' ', sp2*' ', sp3*' ')
        print prval + "\n" + "="*(len(prval)+5) + "\n"

    for k in range(len(sta)):
        val = sta[k]
        print "%s %s %s %s %s %s %s" % (val[0], ' '*(sp1-len(val[0])+4),
                                        val[1], ' '*(sp2-len(val[1])+5),
                                        val[2], ' '*(sp3-len(val[2])+5),
                                        val[3])
    print "\nUpper bound on total bytes  =       %d" % totalbytes
    return

#-----------------------------------------------------------------------------

def test(level=10):
    from scipy_test.testing import module_test
    module_test(__name__,__file__,level=level)

def test_suite(level=1):
    from scipy_test.testing import module_test_suite
    return module_test_suite(__name__,__file__,level=level)

<commit_msg>Added multifactorial function.  Fixed object array bug with insert.<commit_after># Functions which are common and require SciPy Base and Level 1 SciPy
# (stats, special, linalg)

# Needs eigenvalues
import Numeric
import types, sys
from scipy import special, stats, linalg
from scipy_base import exp, amin, amax, ravel, asarray, cast, arange, \
     ones, NewAxis, transpose, hstack, product, array, typename, where, \
     zeros, extract, insert, pi, sqrt
import scipy_base.fastumath

__all__ = ['factorial','factorial2','factorialk','comb','rand','randn','who',
           'lena','central_diff_weights', 'derivative']

def factorial(n,exact=0):
    """n! = special.gamma(n+1)

    If exact==0, then floating point precision is used, otherwise
    exact long integer is computed.

    Notes:
      - Array argument accepted only for exact=0 case.
      - If n<0, the return value is 0.
    """
    if exact:
        if n < 0:
            return 0L
        n = long(n)
        val = 1L
        k = 1L
        while (k < n+1L):
            val = val*k
            k += 1
        return val
    else:
        n = asarray(n)
        sv = special.errprint(0)
        vals = special.gamma(n+1)
        sv = special.errprint(sv)
        return where(n>=0,vals,0)

def factorial2(n,exact=0):
    """n!! = special.gamma(n/2+1)*2**((m+1)/2)/sqrt(pi)  n odd
           = 2**(n) * n!                                 n even

    If exact==0, then floating point precision is used, otherwise
    exact long integer is computed.

    Notes:
      - Array argument accepted only for exact=0 case.
      - If n<0, the return value is 0.
    """
    if exact:
        if n < -1:
            return 0L
        if n <= 0:
            return 1L
        n = long(n)
        val = 1L
        k = n
        while (k > 0):
            val = val*k
            k -= 2
        return val
    else:
        n = asarray(n)
        vals = zeros(n.shape,'d')
        cond1 = (n % 2) & (n >= -1)
        cond2 = (1-(n % 2)) & (n >= -1)
        oddn = extract(n,cond1)
        evenn = extract(n,cond2)
        nd2o = oddn / 2.0
        nd2e = evenn / 2.0
        insert(vals,cond1,special.gamma(nd2o+1)/sqrt(pi)*pow(2.0,nd2o+0.5))
        insert(vals,cond2,special.gamma(nd2e+1) * pow(2.0,nd2e))
        return vals

def factorialk(n,k,exact=1):
    """n(!!...!)  = multifactorial of order k
        k times
    """
    if exact:
        if n < 1-k:
            return 0L
        if n<=0:
            return 1L
        n = long(n)
        val = 1L
        j = n
        while (j > 0):
            val = val*j
            j -= k
        return val
    else:
        raise NotImplementedError

def comb(N,k,exact=0):
    """Combinations of N things taken k at a time.

    If exact==0, then floating point precision is used, otherwise
    exact long integer is computed.

    Notes:
      - Array arguments accepted only for exact=0 case.
      - If k > N, N < 0, or k < 0, then a 0 is returned.
    """
    if exact:
        if (k > N) or (N < 0) or (k < 0):
            return 0L
        N,k = map(long,(N,k))
        top = N
        val = 1L
        while (top > (N-k)):
            val *= top
            top -= 1
        n = 1L
        while (n < k+1L):
            val /= n
            n += 1
        return val
    else:
        k,N = asarray(k), asarray(N)
        lgam = special.gammaln
        cond = (k <= N) & (N >= 0) & (k >= 0)
        sv = special.errprint(0)
        vals = exp(lgam(N+1) - lgam(N-k+1) - lgam(k+1))
        sv = special.errprint(sv)
        return where(cond, vals, 0.0)

def central_diff_weights(Np,ndiv=1):
    """Return weights for an Np-point central derivative of order ndiv
       assuming equally-spaced function points.

       If weights are in the vector w, then
       derivative is w[0] * f(x-ho*dx) + ... + w[-1] * f(x+h0*dx)

       Can be inaccurate for large number of points.
    """
    assert (Np >= ndiv+1), "Number of points must be at least the derivative order + 1."
    assert (Np % 2 == 1), "Odd-number of points only."
    ho = Np >> 1
    x = arange(-ho,ho+1.0)
    x = x[:,NewAxis]
    X = x**0.0
    for k in range(1,Np):
        X = hstack([X,x**k])
    w = product(arange(1,ndiv+1))*linalg.inv(X)[ndiv]
    return w

def derivative(func,x0,dx=1.0,n=1,args=(),order=3):
    """Given a function, use an N-point central differenece
       formula with spacing dx to compute the nth derivative at
       x0, where N is the value of order and must be odd.

       Warning: Decreasing the step size too small can result in
       round-off error.
    """
    assert (order >= n+1), "Number of points must be at least the derivative order + 1."
    assert (order % 2 == 1), "Odd number of points only."
    # pre-computed for n=1 and 2 and low-order for speed.
    if n==1:
        if order == 3:
            weights = array([-1,0,1])/2.0
        elif order == 5:
            weights = array([1,-8,0,8,-1])/12.0
        elif order == 7:
            weights = array([-1,9,-45,0,45,-9,1])/60.0
        elif order == 9:
            weights = array([3,-32,168,-672,0,672,-168,32,-3])/840.0
        else:
            weights = central_diff_weights(order,1)
    elif n==2:
        if order == 3:
            weights = array([1,-2.0,1])
        elif order == 5:
            weights = array([-1,16,-30,16,-1])/12.0
        elif order == 7:
            weights = array([2,-27,270,-490,270,-27,2])/180.0
        elif order == 9:
            weights = array([-9,128,-1008,8064,-14350,8064,-1008,128,-9])/5040.0
        else:
            weights = central_diff_weights(order,2)
    else:
        weights = central_diff_weights(order, n)
    val = 0.0
    ho = order >> 1
    for k in range(order):
        val += weights[k]*func(x0+(k-ho)*dx,*args)
    return val / product((dx,)*n)

def rand(*args):
    """rand(d1,...,dn) returns a matrix of the given dimensions
    which is initialized to random numbers from a uniform distribution
    in the range [0,1).
    """
    return stats.random(args)

def randn(*args):
    """u = randn(d0,d1,...,dn) returns zero-mean, unit-variance Gaussian
    random numbers in an array of size (d0,d1,...,dn)."""
    return stats.stnorm(size=args)

def lena():
    import cPickle, os
    d,junk = os.path.split(os.path.abspath(scipy.__file__))
    fname = os.path.join(d,'plt','lena.dat')
    f = open(fname,'rb')
    lena = scipy.array(cPickle.load(f))
    f.close()
    return lena

#-----------------------------------------------------------------------------
# Matlab like functions for output and information on the variables used.
#-----------------------------------------------------------------------------

def who(vardict=None):
    """Print the Numeric arrays in the given dictionary (or globals() if None).
    """
    if vardict is None:
        print "Pass in a dictionary:  who(globals())"
        return
    sta = []
    cache = {}
    for name in vardict.keys():
        if isinstance(vardict[name],Numeric.ArrayType):
            var = vardict[name]
            idv = id(var)
            if idv in cache.keys():
                namestr = name + " (%s)" % cache[idv]
                original=0
            else:
                cache[idv] = name
                namestr = name
                original=1
            shapestr = " x ".join(map(str, var.shape))
            bytestr = str(var.itemsize()*Numeric.product(var.shape))
            sta.append([namestr, shapestr, bytestr, typename(var.typecode()), original])

    maxname = 0
    maxshape = 0
    maxbyte = 0
    totalbytes = 0
    for k in range(len(sta)):
        val = sta[k]
        if maxname < len(val[0]):
            maxname = len(val[0])
        if maxshape < len(val[1]):
            maxshape = len(val[1])
        if maxbyte < len(val[2]):
            maxbyte = len(val[2])
        if val[4]:
            totalbytes += int(val[2])

    max = Numeric.maximum
    if len(sta) > 0:
        sp1 = max(10,maxname)
        sp2 = max(10,maxshape)
        sp3 = max(10,maxbyte)
        prval = "Name %s Shape %s Bytes %s Type" % (sp1*' ', sp2*' ', sp3*' ')
        print prval + "\n" + "="*(len(prval)+5) + "\n"

    for k in range(len(sta)):
        val = sta[k]
        print "%s %s %s %s %s %s %s" % (val[0], ' '*(sp1-len(val[0])+4),
                                        val[1], ' '*(sp2-len(val[1])+5),
                                        val[2], ' '*(sp3-len(val[2])+5),
                                        val[3])
    print "\nUpper bound on total bytes  =       %d" % totalbytes
    return

#-----------------------------------------------------------------------------

def test(level=10):
    from scipy_test.testing import module_test
    module_test(__name__,__file__,level=level)

def test_suite(level=1):
    from scipy_test.testing import module_test_suite
    return module_test_suite(__name__,__file__,level=level)

<|endoftext|>


<commit_before># The Grand Battle to ePub Converterator!
# Obeys the PEP 8 Python "good practice" conventions. Ugh!
# Created by The Protractor Ninja

import re
import argparse
import urllib2
from bs4 import BeautifulSoup, element

class Thread(object):
    """
    A class representing an MSPA forum thread and its respective posts.

    Note that post numbers begin at one, i.e. the original post is #1.
    """

    def __init__(self, thread_number):
        """
        Create a thread instance using the given thread number.

        If the thread number is bad, then a URLError will be raised.
        """
        try:
            html = urllib2.urlopen(r"http://www.mspaforums.com/printthread.php"
                                   r"?t={0}&pp=9999".format(thread_number))
        except urllib2.URLError as error:
            raise error
        self.soup = BeautifulSoup(html, "lxml")
        self._posts = self.soup.find_all("li", id=re.compile('post_.*'))
        self.author = self.get_author()
        self.total_posts = len(self._posts)

        # Create a dictionary mapping an author to a list containing the
        # post numbers of all of their posts.
        self._author_list = dict()
        for post in self._posts:
            author = self.get_author(post).lower()
            post_number = self.get_post_number(post)
            if author in self._author_list:
                self._author_list[author].append(post_number)
            else:
                self._author_list[author] = [post_number]

        # Create a list of authors in the thread, ordered by number of
        # posts made. The aforementioned _author_list dictionary cannot
        # be used because dictionaries do not have order.
        #
        # The author with the most posts will be listed first.
        self._author_ranks = []
        for author, post_numbers in iter(self._author_list.items()):
            self._author_ranks.append((author, len(post_numbers)))
        self._author_ranks = map(lambda x: x[0], sorted(self._author_ranks,
                                 key=lambda x: x[1], reverse=True))

    def get_author(self, post_number=1):
        """
        Find the author of a post.
        """
        post_number = self.validate_post_number(post_number)
        author = self.get_post(post_number).find("span", "username")
        if author:
            return str(author.get_text().strip().lower())
        else:
            return "~NULL AUTHOR {0}~".format(post_number)

    def get_post_number(self, post):
        """
        Determine the post number of a given post Tag.
        """
        return int(post['id'].replace("post_", ""))

    def get_post(self, post_number=1):
        """
        Get a Tag object for a post of given number.

        A negative number starts from the last post, i.e. -1 is the
        final post. An argument of 0 or 1 returns the first post.

        Returns the original post by default.
        """
        post_number = self.validate_post_number(post_number)
        if post_number <= 0:
            return self._posts[post_number]
        else:
            return self._posts[post_number - 1]

    def find_posts_by_author(self, author=None):
        """
        Find all posts by a given author.

        Returns an empty list if there are no posts by the given author.
        """
        if author is None:
            author = self.author
        author = author.lower()
        return map(lambda x: self.get_post(x), self.find_post_numbers_by_author
                                               (author))

    def find_post_numbers_by_author(self, author=None):
        """
        Find the numbers of all posts by a given author.

        Returns an empty list if there are no posts by the given author.
        """
        if author is None:
            author = self.author
        author = author.lower()
        post_list = []
        if author in self._author_list:
            post_list = self._author_list[author][:]
        return post_list

    def get_ranked_authors_list(self, *args):
        """
        Get a list of thread authors by decreasing post amount.

        Optionally, authors to ignore in this list may be passed in as
        arguments in the variable length argument format.
        """
        return filter(lambda arg: not arg in args, self._author_ranks)

    def get_post_content(self, post_number=1):
        """
        Get the text content of a post.

        Ignores extra author and date data, as well as spoiler tags and
        most HTML markup. Keeps  and  tags for future epub usage.

        Defaults to the first post if no post number is specified.

        !!! UNFINISHED !!!
        """
        post_number = self.validate_post_number(post_number)
        post = self.get_post(post_number)

        post_text = str(post)
        post_text = (post_text.replace("", "~~~~~b~~~~~")
                              .replace("", "~~~~~/b~~~~~"))
        post_text = (post_text.replace("", "~~~~~i~~~~~")
                              .replace("", "~~~~~/i~~~~~"))

    def validate_post_number(self, post_number):
        """
        Determine if a "post number" is a post Tag or an integer.

        If the "number" is in fact a Tag, then return the post number
        corresponding to said Tag.
        """
        if type(post_number) is element.Tag:
            post_number = self.get_post_number(post_number)
        return post_number

class GrandBattle(Thread):
    """
    Represents an instance of an MSPAF Grand Battle, documenting rounds,
    players and their characters, narrator(s), and other information.

    Subclasses the Thread class to keep basic functions separate from
    specifically Thread-oriented functions.
    """

    def __init__(self, thread_number):
        super(GrandBattle, self).__init__(thread_number)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Absorb an MSPA Forum thread "
                                                 "and turn it into an ebook.")
    parser.add_argument('data', help="A MSPAF URL or thread ID number.")
    argument_data = parser.parse_args().data
    if re.match(r"^[\d]+$", argument_data):
        thread_number = argument_data
    else:
        url_test = re.match(r"https?://www\.mspaforums\.com/(?:show|print)"
                            r"thread\.php\?(?:t=)?([\d]+)", argument_data)
        if not url_test:
            raise ValueError("{0} is not a thread number or valid URL."
                             .format(argument_data))
        thread_number = url_test.group(1)

    # Open up a gateway to the compost pile file for testing!
    compost_pile = open("C:/users/austin/root/tmp/temp.txt", "w")

    grand_battle = GrandBattle(thread_number)
    compost_pile.write(grand_battle.soup.prettify("utf-8"))
    print grand_battle.get_ranked_authors_list()
<commit_msg>"Completed" get_post_content()<commit_after># The Grand Battle to ePub Converterator!
# Obeys the PEP 8 Python "good practice" conventions. Ugh!
# Created by The Protractor Ninja

import re
import argparse
import urllib2
from bs4 import BeautifulSoup, element

class Thread(object):
    """
    A class representing an MSPA forum thread and its respective posts.

    Note that post numbers begin at one, i.e. the original post is #1.
    """

    def __init__(self, thread_number):
        """
        Create a thread instance using the given thread number.

        If the thread number is bad, then a URLError will be raised.
        """
        try:
            html = urllib2.urlopen(r"http://www.mspaforums.com/printthread.php"
                                   r"?t={0}&pp=9999".format(thread_number))
        except urllib2.URLError as error:
            raise error
        self.soup = BeautifulSoup(html, "lxml")
        self._posts = self.soup.find_all("li", id=re.compile('post_.*'))
        self.author = self.get_author()
        self.total_posts = len(self._posts)

        # Create a dictionary mapping an author to a list containing the
        # post numbers of all of their posts.
        self._author_list = dict()
        for post in self._posts:
            author = self.get_author(post).lower()
            post_number = self.get_post_number(post)
            if author in self._author_list:
                self._author_list[author].append(post_number)
            else:
                self._author_list[author] = [post_number]

        # Create a list of authors in the thread, ordered by number of
        # posts made. The aforementioned _author_list dictionary cannot
        # be used because dictionaries do not have order.
        #
        # The author with the most posts will be listed first.
        self._author_ranks = []
        for author, post_numbers in iter(self._author_list.items()):
            self._author_ranks.append((author, len(post_numbers)))
        self._author_ranks = map(lambda x: x[0], sorted(self._author_ranks,
                                 key=lambda x: x[1], reverse=True))

    def get_author(self, post_number=1):
        """
        Find the author of a post.
        """
        post_number = self.validate_post_number(post_number)
        author = self.get_post(post_number).find("span", "username")
        if author:
            return str(author.get_text().strip().lower())
        else:
            return "~NULL AUTHOR {0}~".format(post_number)

    def get_post_number(self, post):
        """
        Determine the post number of a given post Tag.
        """
        return int(post['id'].replace("post_", ""))

    def get_post(self, post_number=1):
        """
        Get a Tag object for a post of given number.

        A negative number starts from the last post, i.e. -1 is the
        final post. An argument of 0 or 1 returns the first post.

        Returns the original post by default.
        """
        post_number = self.validate_post_number(post_number)
        if post_number <= 0:
            return self._posts[post_number]
        else:
            return self._posts[post_number - 1]

    def find_posts_by_author(self, author=None):
        """
        Find all posts by a given author.

        Returns an empty list if there are no posts by the given author.
        """
        if author is None:
            author = self.author
        author = author.lower()
        return map(lambda x: self.get_post(x), self.find_post_numbers_by_author
                                               (author))

    def find_post_numbers_by_author(self, author=None):
        """
        Find the numbers of all posts by a given author.

        Returns an empty list if there are no posts by the given author.
        """
        if author is None:
            author = self.author
        author = author.lower()
        post_list = []
        if author in self._author_list:
            post_list = self._author_list[author][:]
        return post_list

    def get_ranked_authors_list(self, *args):
        """
        Get a list of thread authors by decreasing post amount.

        Optionally, authors to ignore in this list may be passed in as
        arguments in the variable length argument format.
        """
        return filter(lambda arg: not arg in args, self._author_ranks)

    def get_post_content(self, post_number=1):
        """
        Get the text content of a post.

        Ignores extra author and date data, as well as spoiler tags and
        most HTML markup. Keeps  and  tags for future epub usage.

        Defaults to the first post if no post number is specified.
        """
        post_number = self.validate_post_number(post_number)
        post = self.get_post(post_number)
        post_content = post.find("blockquote", "restore")

        post_text = str(post_content)
        post_text = (post_text.replace("", "~~~~~b~~~~~")
                              .replace("", "~~~~~/b~~~~~"))
        post_text = (post_text.replace("", "~~~~~i~~~~~")
                              .replace("", "~~~~~/i~~~~~"))

        post_content = BeautifulSoup(post_text)
        post_text = post_content.get_text()
        post_text = (post_text.replace("~~~~~b~~~~~", "")
                              .replace("~~~~~/b~~~~~", ""))
        post_text = (post_text.replace("~~~~~i~~~~~", "")
                              .replace("~~~~~/i~~~~~", ""))
        return post_text

    def validate_post_number(self, post_number):
        """
        Determine if a "post number" is a post Tag or an integer.

        If the "number" is in fact a Tag, then return the post number
        corresponding to said Tag.
        """
        if type(post_number) is element.Tag:
            post_number = self.get_post_number(post_number)
        return post_number

class GrandBattle(Thread):
    """
    Represents an instance of an MSPAF Grand Battle, documenting rounds,
    players and their characters, narrator(s), and other information.

    Subclasses the Thread class to keep basic functions separate from
    specifically Thread-oriented functions.
    """

    def __init__(self, thread_number):
        super(GrandBattle, self).__init__(thread_number)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Absorb an MSPA Forum thread "
                                                 "and turn it into an ebook.")
    parser.add_argument('data', help="A MSPAF URL or thread ID number.")
    argument_data = parser.parse_args().data
    if re.match(r"^[\d]+$", argument_data):
        thread_number = argument_data
    else:
        url_test = re.match(r"https?://www\.mspaforums\.com/(?:show|print)"
                            r"thread\.php\?(?:t=)?([\d]+)", argument_data)
        if not url_test:
            raise ValueError("{0} is not a thread number or valid URL."
                             .format(argument_data))
        thread_number = url_test.group(1)

    # Open up a gateway to the compost pile file for testing!
    compost_pile = open("C:/users/austin/root/tmp/temp.txt", "w")

    grand_battle = GrandBattle(thread_number)
    #compost_pile.write(grand_battle.soup.prettify("utf-8"))
    #print grand_battle.get_ranked_authors_list()
    compost_pile.write(grand_battle.get_post(1).prettify("utf-8"))
    compost_pile.write("~ ~ ~~~ NEXT LINE BEGINS GETCONTENT ~~~ ~ ~\n")
    compost_pile.write(grand_battle.get_post_content(1).encode("utf-8"))
    #for string in grand_battle.get_post(1).stripped_strings:
    #    compost_pile.write("{0}\n".format(string.encode("utf-8")))
<|endoftext|>


<commit_before>
import struct
from cStringIO import StringIO

TAG_TYPE_METADATA = 18

##################################################
# AMF0
##################################################

AMF_TYPE_NUMBER = 0x00
AMF_TYPE_BOOLEAN = 0x01
AMF_TYPE_STRING = 0x02
AMF_TYPE_OBJECT = 0x03
AMF_TYPE_MOVIECLIP = 0x04
AMF_TYPE_NULL = 0x05
AMF_TYPE_UNDEFINED = 0x06
AMF_TYPE_REFERENCE = 0x07
AMF_TYPE_MIXED_ARRAY = 0x08
AMF_TYPE_END_OF_OBJECT = 0x09
AMF_TYPE_ARRAY = 0x0A
AMF_TYPE_DATE = 0x0B
AMF_TYPE_LONG_STRING = 0x0C
AMF_TYPE_UNSUPPORTED = 0x0D
AMF_TYPE_RECORDSET = 0x0E
AMF_TYPE_XML = 0x0F
AMF_TYPE_CLASS_OBJECT = 0x10
AMF_TYPE_AMF3_OBJECT = 0x11

class ECMAObject:
	def __init__(self, max_number):
		self.max_number = max_number
		self.data = []
		self.map = {}
	def put(self, k, v):
		self.data.append((k, v))
		self.map[k] = v
	def get(self, k):
		return self.map[k]
	def set(self, k, v):
		for i in range(len(self.data)):
			if self.data[i][0] == k:
				self.data[i] = (k, v)
				break
		else:
			raise KeyError(k)
		self.map[k] = v
	def keys(self):
		return self.map.keys()
	def __str__(self):
		return 'ECMAObject<'+repr(self.map)+'>'
	def __eq__(self, other):
		return self.max_number == other.max_number and self.data == other.data

def read_amf_number(stream):
	return struct.unpack('>d', stream.read(8))[0]

def read_amf_boolean(stream):
	b = read_byte(stream)
	assert b in (0, 1)
	return bool(b)

def read_amf_string(stream):
	n = struct.unpack('>H', stream.read(2))[0]
	s = stream.read(n)
	assert len(s) == n
	return s.decode('utf-8')

def read_amf_object(stream):
	obj = {}
	while True:
		k = read_amf_string(stream)
		if not k:
			assert read_byte(stream) == AMF_TYPE_END_OF_OBJECT
			break
		v = read_amf(stream)
		obj[k] = v
	return obj

def read_amf_mixed_array(stream):
	max_number = read_uint(stream)
	mixed_results = ECMAObject(max_number)
	while True:
		k = read_amf_string(stream)
		if not k:
			assert read_byte(stream) == AMF_TYPE_END_OF_OBJECT
			break
		v = read_amf(stream)
		mixed_results.put(k, v)
	assert len(mixed_results.data) == max_number
	return mixed_results

def read_amf_array(stream):
	n = read_uint(stream)
	v = []
	for i in range(n):
		v.append(read_amf(stream))
	return v

amf_readers = {
		AMF_TYPE_NUMBER: read_amf_number,
		AMF_TYPE_BOOLEAN: read_amf_boolean,
		AMF_TYPE_STRING: read_amf_string,
		AMF_TYPE_OBJECT: read_amf_object,
		AMF_TYPE_MIXED_ARRAY: read_amf_mixed_array,
		AMF_TYPE_ARRAY: read_amf_array,
}

def read_amf(stream):
	return amf_readers[read_byte(stream)](stream)

def write_amf_number(stream, v):
	stream.write(struct.pack('>d', v))

def write_amf_boolean(stream, v):
	if v:
		stream.write('\x01')
	else:
		stream.write('\x00')

def write_amf_string(stream, s):
	s = s.encode('utf-8')
	stream.write(struct.pack('>H', len(s)))
	stream.write(s)

def write_amf_object(stream, o):
	for k in o:
		write_amf_string(stream, k)
		write_amf(stream, o[k])
	write_amf_string(stream, '')
	write_byte(stream, AMF_TYPE_END_OF_OBJECT)

def write_amf_mixed_array(stream, o):
	write_uint(stream, o.max_number)
	for k, v in o.data:
		write_amf_string(stream, k)
		write_amf(stream, v)
	write_amf_string(stream, '')
	write_byte(stream, AMF_TYPE_END_OF_OBJECT)

def write_amf_array(stream, o):
	write_uint(stream, len(o))
	for v in o:
		write_amf(stream, v)

amf_writers_tags = {
		float: AMF_TYPE_NUMBER,
		bool: AMF_TYPE_BOOLEAN,
		unicode: AMF_TYPE_STRING,
		dict: AMF_TYPE_OBJECT,
		ECMAObject: AMF_TYPE_MIXED_ARRAY,
		list: AMF_TYPE_ARRAY,
}

amf_writers = {
		AMF_TYPE_NUMBER: write_amf_number,
		AMF_TYPE_BOOLEAN: write_amf_boolean,
		AMF_TYPE_STRING: write_amf_string,
		AMF_TYPE_OBJECT: write_amf_object,
		AMF_TYPE_MIXED_ARRAY: write_amf_mixed_array,
		AMF_TYPE_ARRAY: write_amf_array,
}

def write_amf(stream, v):
	if isinstance(v, ECMAObject):
		tag = amf_writers_tags[ECMAObject]
	else:
		tag = amf_writers_tags[type(v)]
	write_byte(stream, tag)
	amf_writers[tag](stream, v)

##################################################
# FLV
##################################################

def read_int(stream):
	return struct.unpack('>i', stream.read(4))[0]

def read_uint(stream):
	return struct.unpack('>I', stream.read(4))[0]

def write_uint(stream, n):
	stream.write(struct.pack('>I', n))

def read_byte(stream):
	return ord(stream.read(1))

def write_byte(stream, b):
	stream.write(chr(b))

def read_unsigned_medium_int(stream):
	x1, x2, x3 = struct.unpack('BBB', stream.read(3))
	return (x1 << 16) | (x2 << 8) | x3

def read_tag(stream):
	# header size: 15 bytes
	header = stream.read(15)
	if len(header) == 4:
		return
	x = struct.unpack('>IBBBBBBBBBBB', header)
	previous_tag_size = x[0]
	data_type = x[1]
	body_size = (x[2] << 16) | (x[3] << 8) | x[4]
	assert body_size < 1024*1024*128, 'tag body size too big (> 128MB)'
	timestamp = (x[5] << 16) | (x[6] << 8) | x[7]
	timestamp += x[8] << 24
	assert x[9:] == (0, 0, 0)
	body = stream.read(body_size)
	return (data_type, timestamp, body_size, body, previous_tag_size)
	#previous_tag_size = read_uint(stream)
	#data_type = read_byte(stream)
	#body_size = read_unsigned_medium_int(stream)
	#assert body_size < 1024*1024*128, 'tag body size too big (> 128MB)'
	#timestamp = read_unsigned_medium_int(stream)
	#timestamp += read_byte(stream) << 24
	#assert read_unsigned_medium_int(stream) == 0
	#body = stream.read(body_size)
	#return (data_type, timestamp, body_size, body, previous_tag_size)

def write_tag(stream, tag):
	data_type, timestamp, body_size, body, previous_tag_size = tag
	write_uint(stream, previous_tag_size)
	write_byte(stream, data_type)
	write_byte(stream, body_size>>16 & 0xff)
	write_byte(stream, body_size>>8  & 0xff)
	write_byte(stream, body_size     & 0xff)
	write_byte(stream, timestamp>>16 & 0xff)
	write_byte(stream, timestamp>>8  & 0xff)
	write_byte(stream, timestamp     & 0xff)
	write_byte(stream, timestamp>>24 & 0xff)
	stream.write('\0\0\0')
	stream.write(body)

def read_flv_header(stream):
	assert stream.read(3) == 'FLV'
	header_version = read_byte(stream)
	assert header_version == 1
	type_flags = read_byte(stream)
	assert type_flags == 5
	data_offset = read_uint(stream)
	assert data_offset == 9

def write_flv_header(stream):
	stream.write('FLV')
	write_byte(stream, 1)
	write_byte(stream, 5)
	write_uint(stream, 9)

def read_meta_data(stream):
	meta_type = read_amf(stream)
	meta = read_amf(stream)
	return meta_type, meta

def read_meta_tag(tag):
	data_type, timestamp, body_size, body, previous_tag_size = tag
	assert data_type == TAG_TYPE_METADATA
	assert timestamp == 0
	assert previous_tag_size == 0
	return read_meta_data(StringIO(body))

def write_meta_data(stream, meta_type, meta_data):
	assert isinstance(meta_type, basesting)
	write_amf(meta_type)
	write_amf(meta_data)

def write_meta_tag(stream, meta_type, meta_data):
	buffer = StringIO()
	write_amf(buffer, meta_type)
	write_amf(buffer, meta_data)
	body = buffer.getvalue()
	write_tag(stream, (TAG_TYPE_METADATA, 0, len(body), body, 0))

##################################################
# main
##################################################

def guess_output(inputs):
	import os.path
	inputs = map(os.path.basename, inputs)
	n = min(map(len, inputs))
	for i in reversed(range(1, n)):
		if len(set(s[:i] for s in inputs)) == 1:
			return inputs[0][:i] + '.flv'
	return 'output.flv'

def concat_flvs(flvs, output=None):
	assert flvs, 'no flv file found'
	import os.path
	if not output:
		output = guess_output(flvs)
	elif os.path.isdir(output):
		output = os.path.join(output, guess_output(flvs))

	print 'Joining %s into %s' % (', '.join(flvs), output)
	ins = [open(flv, 'rb') for flv in flvs]
	for stream in ins:
		read_flv_header(stream)
	meta_tags = map(read_tag, ins)
	metas = map(read_meta_tag, meta_tags)
	meta_types, metas = zip(*metas)
	assert len(set(meta_types)) == 1
	meta_type = meta_types[0]

	# must merge fields: duration
	# TODO: check other meta info, update other meta info
	total_duration = sum(meta.get('duration') for meta in metas)
	meta_data = metas[0]
	meta_data.set('duration', total_duration)

	out = open(output, 'wb')
	write_flv_header(out)
	write_meta_tag(out, meta_type, meta_data)
	timestamp_start = 0
	for stream in ins:
		while True:
			tag = read_tag(stream)
			if tag:
				data_type, timestamp, body_size, body, previous_tag_size = tag
				timestamp += timestamp_start
				tag = data_type, timestamp, body_size, body, previous_tag_size
				write_tag(out, tag)
			else:
				break
		timestamp_start = timestamp
	write_uint(out, previous_tag_size)

	return output

def usage():
	print 'python flv_join.py flv... --output target.flv'

if __name__ == '__main__':
	import sys, getopt
	try:
		opts, args = getopt.getopt(sys.argv[1:], "ho:", ["help", "output="])
	except getopt.GetoptError, err:
		usage()
		sys.exit(1)
	output = None
	verbose = False
	for o, a in opts:
		if o in ("-h", "--help"):
			usage()
			sys.exit()
		elif o in ("-o", "--output"):
			output = a
		else:
			usage()
			sys.exit(1)
	if not args:
		usage()
		sys.exit(1)

	concat_flvs(args, output)

<commit_msg>fix help<commit_after>
import struct
from cStringIO import StringIO

TAG_TYPE_METADATA = 18

##################################################
# AMF0
##################################################

AMF_TYPE_NUMBER = 0x00
AMF_TYPE_BOOLEAN = 0x01
AMF_TYPE_STRING = 0x02
AMF_TYPE_OBJECT = 0x03
AMF_TYPE_MOVIECLIP = 0x04
AMF_TYPE_NULL = 0x05
AMF_TYPE_UNDEFINED = 0x06
AMF_TYPE_REFERENCE = 0x07
AMF_TYPE_MIXED_ARRAY = 0x08
AMF_TYPE_END_OF_OBJECT = 0x09
AMF_TYPE_ARRAY = 0x0A
AMF_TYPE_DATE = 0x0B
AMF_TYPE_LONG_STRING = 0x0C
AMF_TYPE_UNSUPPORTED = 0x0D
AMF_TYPE_RECORDSET = 0x0E
AMF_TYPE_XML = 0x0F
AMF_TYPE_CLASS_OBJECT = 0x10
AMF_TYPE_AMF3_OBJECT = 0x11

class ECMAObject:
	def __init__(self, max_number):
		self.max_number = max_number
		self.data = []
		self.map = {}
	def put(self, k, v):
		self.data.append((k, v))
		self.map[k] = v
	def get(self, k):
		return self.map[k]
	def set(self, k, v):
		for i in range(len(self.data)):
			if self.data[i][0] == k:
				self.data[i] = (k, v)
				break
		else:
			raise KeyError(k)
		self.map[k] = v
	def keys(self):
		return self.map.keys()
	def __str__(self):
		return 'ECMAObject<'+repr(self.map)+'>'
	def __eq__(self, other):
		return self.max_number == other.max_number and self.data == other.data

def read_amf_number(stream):
	return struct.unpack('>d', stream.read(8))[0]

def read_amf_boolean(stream):
	b = read_byte(stream)
	assert b in (0, 1)
	return bool(b)

def read_amf_string(stream):
	n = struct.unpack('>H', stream.read(2))[0]
	s = stream.read(n)
	assert len(s) == n
	return s.decode('utf-8')

def read_amf_object(stream):
	obj = {}
	while True:
		k = read_amf_string(stream)
		if not k:
			assert read_byte(stream) == AMF_TYPE_END_OF_OBJECT
			break
		v = read_amf(stream)
		obj[k] = v
	return obj

def read_amf_mixed_array(stream):
	max_number = read_uint(stream)
	mixed_results = ECMAObject(max_number)
	while True:
		k = read_amf_string(stream)
		if not k:
			assert read_byte(stream) == AMF_TYPE_END_OF_OBJECT
			break
		v = read_amf(stream)
		mixed_results.put(k, v)
	assert len(mixed_results.data) == max_number
	return mixed_results

def read_amf_array(stream):
	n = read_uint(stream)
	v = []
	for i in range(n):
		v.append(read_amf(stream))
	return v

amf_readers = {
		AMF_TYPE_NUMBER: read_amf_number,
		AMF_TYPE_BOOLEAN: read_amf_boolean,
		AMF_TYPE_STRING: read_amf_string,
		AMF_TYPE_OBJECT: read_amf_object,
		AMF_TYPE_MIXED_ARRAY: read_amf_mixed_array,
		AMF_TYPE_ARRAY: read_amf_array,
}

def read_amf(stream):
	return amf_readers[read_byte(stream)](stream)

def write_amf_number(stream, v):
	stream.write(struct.pack('>d', v))

def write_amf_boolean(stream, v):
	if v:
		stream.write('\x01')
	else:
		stream.write('\x00')

def write_amf_string(stream, s):
	s = s.encode('utf-8')
	stream.write(struct.pack('>H', len(s)))
	stream.write(s)

def write_amf_object(stream, o):
	for k in o:
		write_amf_string(stream, k)
		write_amf(stream, o[k])
	write_amf_string(stream, '')
	write_byte(stream, AMF_TYPE_END_OF_OBJECT)

def write_amf_mixed_array(stream, o):
	write_uint(stream, o.max_number)
	for k, v in o.data:
		write_amf_string(stream, k)
		write_amf(stream, v)
	write_amf_string(stream, '')
	write_byte(stream, AMF_TYPE_END_OF_OBJECT)

def write_amf_array(stream, o):
	write_uint(stream, len(o))
	for v in o:
		write_amf(stream, v)

amf_writers_tags = {
		float: AMF_TYPE_NUMBER,
		bool: AMF_TYPE_BOOLEAN,
		unicode: AMF_TYPE_STRING,
		dict: AMF_TYPE_OBJECT,
		ECMAObject: AMF_TYPE_MIXED_ARRAY,
		list: AMF_TYPE_ARRAY,
}

amf_writers = {
		AMF_TYPE_NUMBER: write_amf_number,
		AMF_TYPE_BOOLEAN: write_amf_boolean,
		AMF_TYPE_STRING: write_amf_string,
		AMF_TYPE_OBJECT: write_amf_object,
		AMF_TYPE_MIXED_ARRAY: write_amf_mixed_array,
		AMF_TYPE_ARRAY: write_amf_array,
}

def write_amf(stream, v):
	if isinstance(v, ECMAObject):
		tag = amf_writers_tags[ECMAObject]
	else:
		tag = amf_writers_tags[type(v)]
	write_byte(stream, tag)
	amf_writers[tag](stream, v)

##################################################
# FLV
##################################################

def read_int(stream):
	return struct.unpack('>i', stream.read(4))[0]

def read_uint(stream):
	return struct.unpack('>I', stream.read(4))[0]

def write_uint(stream, n):
	stream.write(struct.pack('>I', n))

def read_byte(stream):
	return ord(stream.read(1))

def write_byte(stream, b):
	stream.write(chr(b))

def read_unsigned_medium_int(stream):
	x1, x2, x3 = struct.unpack('BBB', stream.read(3))
	return (x1 << 16) | (x2 << 8) | x3

def read_tag(stream):
	# header size: 15 bytes
	header = stream.read(15)
	if len(header) == 4:
		return
	x = struct.unpack('>IBBBBBBBBBBB', header)
	previous_tag_size = x[0]
	data_type = x[1]
	body_size = (x[2] << 16) | (x[3] << 8) | x[4]
	assert body_size < 1024*1024*128, 'tag body size too big (> 128MB)'
	timestamp = (x[5] << 16) | (x[6] << 8) | x[7]
	timestamp += x[8] << 24
	assert x[9:] == (0, 0, 0)
	body = stream.read(body_size)
	return (data_type, timestamp, body_size, body, previous_tag_size)
	#previous_tag_size = read_uint(stream)
	#data_type = read_byte(stream)
	#body_size = read_unsigned_medium_int(stream)
	#assert body_size < 1024*1024*128, 'tag body size too big (> 128MB)'
	#timestamp = read_unsigned_medium_int(stream)
	#timestamp += read_byte(stream) << 24
	#assert read_unsigned_medium_int(stream) == 0
	#body = stream.read(body_size)
	#return (data_type, timestamp, body_size, body, previous_tag_size)

def write_tag(stream, tag):
	data_type, timestamp, body_size, body, previous_tag_size = tag
	write_uint(stream, previous_tag_size)
	write_byte(stream, data_type)
	write_byte(stream, body_size>>16 & 0xff)
	write_byte(stream, body_size>>8  & 0xff)
	write_byte(stream, body_size     & 0xff)
	write_byte(stream, timestamp>>16 & 0xff)
	write_byte(stream, timestamp>>8  & 0xff)
	write_byte(stream, timestamp     & 0xff)
	write_byte(stream, timestamp>>24 & 0xff)
	stream.write('\0\0\0')
	stream.write(body)

def read_flv_header(stream):
	assert stream.read(3) == 'FLV'
	header_version = read_byte(stream)
	assert header_version == 1
	type_flags = read_byte(stream)
	assert type_flags == 5
	data_offset = read_uint(stream)
	assert data_offset == 9

def write_flv_header(stream):
	stream.write('FLV')
	write_byte(stream, 1)
	write_byte(stream, 5)
	write_uint(stream, 9)

def read_meta_data(stream):
	meta_type = read_amf(stream)
	meta = read_amf(stream)
	return meta_type, meta

def read_meta_tag(tag):
	data_type, timestamp, body_size, body, previous_tag_size = tag
	assert data_type == TAG_TYPE_METADATA
	assert timestamp == 0
	assert previous_tag_size == 0
	return read_meta_data(StringIO(body))

def write_meta_data(stream, meta_type, meta_data):
	assert isinstance(meta_type, basesting)
	write_amf(meta_type)
	write_amf(meta_data)

def write_meta_tag(stream, meta_type, meta_data):
	buffer = StringIO()
	write_amf(buffer, meta_type)
	write_amf(buffer, meta_data)
	body = buffer.getvalue()
	write_tag(stream, (TAG_TYPE_METADATA, 0, len(body), body, 0))

##################################################
# main
##################################################

def guess_output(inputs):
	import os.path
	inputs = map(os.path.basename, inputs)
	n = min(map(len, inputs))
	for i in reversed(range(1, n)):
		if len(set(s[:i] for s in inputs)) == 1:
			return inputs[0][:i] + '.flv'
	return 'output.flv'

def concat_flvs(flvs, output=None):
	assert flvs, 'no flv file found'
	import os.path
	if not output:
		output = guess_output(flvs)
	elif os.path.isdir(output):
		output = os.path.join(output, guess_output(flvs))

	print 'Joining %s into %s' % (', '.join(flvs), output)
	ins = [open(flv, 'rb') for flv in flvs]
	for stream in ins:
		read_flv_header(stream)
	meta_tags = map(read_tag, ins)
	metas = map(read_meta_tag, meta_tags)
	meta_types, metas = zip(*metas)
	assert len(set(meta_types)) == 1
	meta_type = meta_types[0]

	# must merge fields: duration
	# TODO: check other meta info, update other meta info
	total_duration = sum(meta.get('duration') for meta in metas)
	meta_data = metas[0]
	meta_data.set('duration', total_duration)

	out = open(output, 'wb')
	write_flv_header(out)
	write_meta_tag(out, meta_type, meta_data)
	timestamp_start = 0
	for stream in ins:
		while True:
			tag = read_tag(stream)
			if tag:
				data_type, timestamp, body_size, body, previous_tag_size = tag
				timestamp += timestamp_start
				tag = data_type, timestamp, body_size, body, previous_tag_size
				write_tag(out, tag)
			else:
				break
		timestamp_start = timestamp
	write_uint(out, previous_tag_size)

	return output

def usage():
	print 'python flv_join.py --output target.flv flv...'

if __name__ == '__main__':
	import sys, getopt
	try:
		opts, args = getopt.getopt(sys.argv[1:], "ho:", ["help", "output="])
	except getopt.GetoptError, err:
		usage()
		sys.exit(1)
	output = None
	verbose = False
	for o, a in opts:
		if o in ("-h", "--help"):
			usage()
			sys.exit()
		elif o in ("-o", "--output"):
			output = a
		else:
			usage()
			sys.exit(1)
	if not args:
		usage()
		sys.exit(1)

	concat_flvs(args, output)

<|endoftext|>