import stat

from antlir.btrfs_diff.extent import Extent
from antlir.btrfs_diff.extents_to_chunks import extents_to_chunks_with_clones
from antlir.btrfs_diff.inode import (
    _repr_time,
    _repr_time_delta,
    _time_delta,
    Chunk,
    ChunkClone,
    Clone,
    Inode,
    InodeOwner,
    InodeUtimes,
)
from antlir.btrfs_diff.inode_id import InodeIDMap

from antlir.tests.common import AntlirTestCase

class InodeTestCase(AntlirTestCase):
    def setUp(self) -> None:
        super().setUp()
        self.id_map = InodeIDMap.new()

    def _complete_inode(self, file_type, **kwargs):
        kwargs.setdefault("mode", 0o644)
        kwargs.setdefault("xattrs", {})
        return Inode(
            file_type=file_type,
            owner=InodeOwner(uid=3, gid=5),
            utimes=InodeUtimes(ctime=(2, 1), mtime=(7, 1), atime=(9, 10**6)),
            **kwargs,
        )

    def test_inode(self) -> None:
        # Shared `repr` boilerplate for the output of `self._complete_inode`.
        O = "o3:5"
        T = "t70/01/01.00:00:02+5+2"
        R = f"m644 {O} {T}"

        # Test a fully populated Inode with some clones -- this is not more
        # exhaustive because we also get lots of coverage from the `for`
        # loop below, and some more some functional coverage from
        # `test_incomplete_inode`.
        extent = Extent.empty()
        extent = extent.write(offset=15, length=5)
        extent = extent.clone(
            to_offset=0, from_extent=extent, from_offset=10, length=10
        )
        chunks_repr = "h5(me:10+5@0)d5(me:15+5@0)h5(me:0+5@0)d5(me:5+5@0)"
        my_id = self.id_map.add_file(self.id_map.next(), b"me")
        ((got_id, chunks),) = extents_to_chunks_with_clones([(my_id, extent)])
        self.assertIs(got_id, my_id)
        self.assertEqual(
            f"(File {R} x'a'='b','c'='d' {chunks_repr})",
            repr(
                self._complete_inode(
                    stat.S_IFREG, chunks=chunks, xattrs={b"a": b"b", b"c": b"d"}
                )
            ),
        )

        # Ensure the maximally "incomplete" case works.
        ino_not_complete = Inode(
            file_type=stat.S_IFREG,
            mode=None,
            owner=None,
            utimes=None,
            xattrs={},
        )
        self.assertEqual("(File)", repr(ino_not_complete))
        with self.assertRaisesRegex(
            RuntimeError, "must have file_type, owner & utimes"
        ):
            ino_not_complete.assert_valid_and_complete()

        # Trip the remaining `assert_valid_and_complete` checks, while also
        # ensuring that `repr` works in each of the cases.  Each failure is
        # preceded by a nearly-identical success.
        # pyre-fixme[6]: For 3rd param expected `Set[ChunkClone]` but got `Tuple[]`.
        chunk_d11 = Chunk(kind=Extent.Kind.DATA, length=11, chunk_clones=())
        for good, expected_repr, kwargs in [
            (1, f"(Dir {R})", {"file_type": stat.S_IFDIR, "mode": 0o644}),
            # bad mode bits
            (
                0,
                f"(Dir m40644 {O} {T})",
                {"file_type": stat.S_IFDIR, "mode": stat.S_IFDIR | 0o644},
            ),
            (1, f"(Dir {R})", {"file_type": stat.S_IFDIR}),
            (
                0,
                f"(16804 {R})",
                {"file_type": stat.S_IFDIR | 0o644},
            ),  # bad file_type bits
            (1, f"(File {R})", {"file_type": stat.S_IFREG, "chunks": ()}),
            (0, f"(File {R})", {"file_type": stat.S_IFREG}),  # lacks `.chunks`
            (1, f"(Char {R} 123)", {"file_type": stat.S_IFCHR, "dev": 0x123}),
            (0, f"(Char {R})", {"file_type": stat.S_IFCHR}),  # lacks `.dev`
            (1, f"(Block {R} 123)", {"file_type": stat.S_IFBLK, "dev": 0x123}),
            (0, f"(Block {R})", {"file_type": stat.S_IFBLK}),  # lacks `.dev`
            (
                1,
                f"(Symlink {O} {T} foo)",
                {"file_type": stat.S_IFLNK, "mode": None, "dest": b"foo"},
            ),
            (
                0,
                f"(Symlink {O} {T})",
                {"file_type": stat.S_IFLNK, "mode": None},
            ),  # lacks `.dest`
            (
                0,
                f"(Symlink {R} foo)",
                {"file_type": stat.S_IFLNK, "dest": b"foo"},
            ),  # `.mode` was set
            # Add extra fields that don't belong to the file-type.
            # The "success" cases for these are already shown above.
            (
                0,
                f"(File {R} d11 123)",
                {
                    "file_type": stat.S_IFREG,
                    "chunks": (chunk_d11,),
                    "dev": 0x123,
                },
            ),
            (
                0,
                f"(Char {R} 123 ohai)",
                {"file_type": stat.S_IFCHR, "dev": 0x123, "dest": b"ohai"},
            ),
            (
                0,
                f"(Block {R} 123 ohai)",
                {"file_type": stat.S_IFBLK, "dev": 0x123, "dest": b"ohai"},
            ),
            (
                0,
                f"(Symlink {O} {T} d11 foo)",
                {
                    "file_type": stat.S_IFLNK,
                    "mode": None,
                    "dest": b"foo",
                    "chunks": (chunk_d11,),
                },
            ),
        ]:
            with self.subTest(repr((good, expected_repr, kwargs))):
                ino = self._complete_inode(kwargs.pop("file_type"), **kwargs)
                self.assertEqual(expected_repr, repr(ino))
                if good:
                    ino.assert_valid_and_complete()
                else:
                    with self.assertRaises(RuntimeError):
                        ino.assert_valid_and_complete()

    def test_chunk_clone(self) -> None:
        clone = Clone(
            inode_id=self.id_map.add_file(self.id_map.next(), b"a"),
            offset=17,
            length=3,
        )
        self.assertEqual("a:17+3", repr(clone))
        self.assertEqual("a:17+3@22", repr(ChunkClone(offset=22, clone=clone)))

    def test_chunk(self) -> None:
        chunk = Chunk(kind=Extent.Kind.DATA, length=12, chunk_clones=set())
        self.assertEqual("(DATA/12)", repr(chunk))
        ino_id = self.id_map.add_file(self.id_map.next(), b"a")
        chunk.chunk_clones.add(
            ChunkClone(offset=3, clone=Clone(inode_id=ino_id, offset=7, length=2))
        )
        self.assertEqual("(DATA/12: a:7+2@3)", repr(chunk))
        chunk.chunk_clones.add(
            ChunkClone(offset=4, clone=Clone(inode_id=ino_id, offset=5, length=6))
        )
        self.assertIn(
            repr(chunk),  # The set can be in one of two orders
            ("(DATA/12: a:7+2@3, a:5+6@4)", "(DATA/12: a:5+6@4, a:7+2@3)"),
        )

    def test_repr_owner(self) -> None:
        self.assertEqual("12:345", repr(InodeOwner(uid=12, gid=345)))

    def test_time_delta(self) -> None:
        self.assertEqual((-1, 999999999), _time_delta((0, 0), (0, 1)))
        self.assertEqual((0, 1), _time_delta((0, 1), (0, 0)))
        self.assertEqual((-4, 999999999), _time_delta((3, 0), (6, 1)))
        self.assertEqual((3, 2), _time_delta((5, 4), (2, 2)))

    def test_repr_time_delta(self) -> None:
        self.assertEqual("-3", _repr_time_delta(-3, 0))
        self.assertEqual("-3", _repr_time_delta(-4, 999999999))
        self.assertEqual("-3.001", _repr_time_delta(-4, 999000000))
        self.assertEqual("+3", _repr_time_delta(3, 0))
        self.assertEqual("+3", _repr_time_delta(3, 1))
        self.assertEqual("+3.001", _repr_time_delta(3, 1000000))

    def test_repr_time(self) -> None:
        self.assertEqual("70/05/23.21:21:18.91", _repr_time(12345678, 910111213))

    def test_repr_utimes(self) -> None:
        self.assertEqual(
            "70/05/23.21:21:18.001+7230.01-3610.6",
            repr(
                InodeUtimes(
                    ctime=(12345678, 1000000),
                    mtime=(12345678 + 7230, 11000000),
                    atime=(12345678 + 7230 - 3611, 411000000),
                )
            ),
        )


import jax.numpy as jnp
from jax import vmap

def aggregation_alpha(imgs,func):
    if func == 'probabilistic':
        img = imgs[0]
        for img_t in imgs[1:]:
            img = img_t + img - img_t * img
    elif func == 'maximum':
        img = jnp.amax(imgs,axis=0)
    elif func == 'average':
        img = jnp.where(imgs>0,imgs/imgs.sum(0),0)
    elif func == 'summation':
        img = imgs.sum(0)
    return img

def aggregation_rgb(imgs:'[n,h,w,3]',depth:'[n,h,w,1]')->'[n,h,w,3]':
    gamma = 1e-1 #[1e-1 ~ 1e4]
    exp_inv_depth = jnp.exp(depth/gamma)*imgs
    exp_inv_depth_sum = exp_inv_depth.sum(0)
    return jnp.where(exp_inv_depth_sum>0,exp_inv_depth/exp_inv_depth_sum,0)

batch_aggregation_rgb = vmap(aggregation_rgb,in_axes=(0,0),out_axes=(0))


# coding: utf-8
"""Helper script for checking versions in the dynamic symbol table.

This script checks that LightGBM library is linked to the appropriate symbol versions.

Linking to newer symbol versions at compile time is problematic because it could result
in built artifacts being unusable on older platforms.

Version history for these symbols can be found at the following:

* GLIBC: https://sourceware.org/glibc/wiki/Glibc%20Timeline
* GLIBCXX: https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html
* OMP/GOMP: https://github.com/gcc-mirror/gcc/blob/master/libgomp/libgomp.map
"""
import re
import sys
from pathlib import Path

def check_dependencies(objdump_string: str) -> None:
    """Check the dynamic symbol versions.

    Parameters
    ----------
    objdump_string : str
        The dynamic symbol table entries of the file (result of `objdump -T` command).
    """
    GLIBC_version = re.compile(r'0{16}[ \(\t]+GLIBC_(\d{1,2})[.](\d{1,3})[.]?\d{,3}[ \)\t]+')
    versions = GLIBC_version.findall(objdump_string)
    assert len(versions) > 1
    for major, minor in versions:
        error_msg = f"found unexpected GLIBC version: '{major}.{minor}'"
        assert int(major) <= 2, error_msg
        assert int(minor) <= 28, error_msg

    GLIBCXX_version = re.compile(r'0{16}[ \(\t]+GLIBCXX_(\d{1,2})[.](\d{1,2})[.]?(\d{,3})[ \)\t]+')
    versions = GLIBCXX_version.findall(objdump_string)
    assert len(versions) > 1
    for major, minor, patch in versions:
        error_msg = f"found unexpected GLIBCXX version: '{major}.{minor}.{patch}'"
        assert int(major) == 3, error_msg
        assert int(minor) == 4, error_msg
        assert patch == '' or int(patch) <= 22, error_msg

    GOMP_version = re.compile(r'0{16}[ \(\t]+G?OMP_(\d{1,2})[.](\d{1,2})[.]?\d{,3}[ \)\t]+')
    versions = GOMP_version.findall(objdump_string)
    assert len(versions) > 1
    for major, minor in versions:
        error_msg = f"found unexpected OMP/GOMP version: '{major}.{minor}'"
        assert int(major) <= 4, error_msg
        assert int(minor) <= 5, error_msg

if __name__ == "__main__":
    check_dependencies(Path(sys.argv[1]).read_text(encoding='utf-8'))


import paho.mqtt.client as mqtt

# MQTT client setup
client = mqtt.Client()
client.connect("localhost", 1883, 60)

# Get the topic and message to publish from user input
topic = "ddps/system/temp4/response"
message = """connect-ack<>{"HeartbeatInterval":10}"""

# Publish the message to the specified topic
client.publish(topic, message)

# Disconnect from the MQTT broker
client.disconnect()


#!/bin/python

# ======== setup ===========

from time import time

start = time()
data = open("19.data").read().split('\n')

# ======== code =======

def replace(molecule, replacement, reverse = False):
	molecules = set()
	x, y = replacement.split(" => ")[::-1] if reverse else replacement.split(" => ")
	for i in range(len(molecule) - len(x) + 1):
		if molecule[i:i + len(x)] == x:
			molecules.add(molecule[:i] + y + molecule[i + len(x):])
	return molecules

def calibrate(molecule, replacements):
	molecules = set()
	for replacement in replacements:
		molecules |= replace(molecule, replacement)
	return len(molecules)

# def generate(target, replacements):
# 	molecules = {'e' : 0}
# 	while not target in molecules:
# 		new_molecules = {}
# 		for molecule, steps in molecules.items():
# 			for replacement in replacements:
# 				for result in replace(molecule, replacement):
# 					if len(result) <= len(target): new_molecules[result] = molecules.get(result, steps + 1)
# 					# if len(result) <= len(target) and result not in molecules: molecules[result] = steps + 1
# 		molecules = new_molecules
# 		print(len(molecules))
# 	return molecules[target]

def generate(target, replacements):
	molecules = {target}
	steps = 0
	while not 'e' in molecules:
		steps += 1
		new_molecules = set()
		for molecule in molecules:
			for replacement in replacements:
				new_molecules |= replace(molecule, replacement, True)
		molecules = new_molecules
		print(len(molecules))
	return steps

molecule = data[-1]
replacements = data[:-2]

print(calibrate(molecule, replacements))
print(generate(molecule, replacements))

print(f"\n===== {time() - start} sec =====")


"""
All Elements in Two Binary Search Trees
Given two binary search trees root1 and root2.

Return a list containing all the integers from both trees sorted in ascending order.

Example 1:

Input: root1 = [2,1,4], root2 = [1,0,3]
Output: [0,1,1,2,3,4]
Example 2:

Input: root1 = [0,-10,10], root2 = [5,1,7,0,2]
Output: [-10,0,0,1,2,5,7,10]
Example 3:

Input: root1 = [], root2 = [5,1,7,0,2]
Output: [0,1,2,5,7]
Example 4:

Input: root1 = [0,-10,10], root2 = []
Output: [-10,0,10]
Example 5:

Input: root1 = [1,null,8], root2 = [8,1]
Output: [1,1,8,8]

Constraints:

Each tree has at most 5000 nodes.
Each node's value is between [-10^5, 10^5].

"""

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def getAllElements(self, root1: TreeNode, root2: TreeNode) -> List[int]:
        s1, s2, res = [], [], []
        while root1 != None or root2!= None or len(s1) > 0 or len(s2) > 0:
            while root1 != None:
                s1.append(root1)
                root1 = root1.left

            while root2 != None:
                s2.append(root2)
                root2 = root2.left

            if len(s2) == 0 or (len(s1) > 0 and s1[-1].val <= s2[-1].val):
                root1 = s1.pop()
                res.append(root1.val)
                root1 = root1.right

            else:
                root2 = s2.pop()
                res.append(root2.val)
                root2 = root2.right

        return res;

All Elements in Two Binary Search Trees.py


from flask import Blueprint
from flask import Response
from flask import abort
from flask import jsonify
from flask import render_template
from flask import request

from app.clients.slack_client import SlackNotification
from app.common.constants import ModuleName
from app.common.decorators import add_customer_activity
from app.helpers.db_helper import add_calculation
from app.pic.ds.CodominantCalculation import Codominant
from app.pic.ds.DominantCalculation import Dominant
from app.userpanel.models import Page

slack_notification = SlackNotification()
pic = Blueprint('pic', __name__)

@pic.route('/pic')
@add_customer_activity
def pic_page():
    return render_template('pic/index.html', title="Polymorphic information content & Heterozygosity")

@pic.route('/pic/send-codominant', methods=['POST'])
def pic_codominant():
    try:
        data = request.get_json()
        co_d = Codominant(data)
        result = co_d.calculate()

        add_calculation(
            module_name=ModuleName.PIC_CODOMINANT, user_data=data, result=result, ip_address=request.remote_addr
        )
        slack_notification.pic_codominant_calculation(data, result, request.remote_addr)

        return jsonify({'data': result})
    except TypeError as e:
        abort(Response(f'Please check type of input data. {e}', 422))
    except Exception as e:
        abort(Response(str(e), 400))

@pic.route('/pic/send-dominant', methods=['POST'])
def pic_dominant():
    try:
        data = request.get_json()
        do = Dominant(data)
        result = do.calculate()

        add_calculation(
            module_name=ModuleName.PIC_DOMINANT, user_data=data, result=result, ip_address=request.remote_addr
        )
        slack_notification.pic_dominant_calculation(data, result, request.remote_addr)

        return jsonify({'data': result})
    except TypeError as e:
        abort(Response(f'Please check type of input data. {e}', 422))
    except Exception as e:
        abort(Response(str(e), 400))

@pic.context_processor
def inject():
    return {'module_desc': Page.query.filter_by(slug=request.path).first()}


import naoqi
from naoqi import ALProxy

IPs = {
	"PABLO_ID" : 	"192.168.0.101",
	"LYRIA_ID" : 	"192.168.0.106" ,
	"KOSHKA_ID" :	"192.168.0.103",
	"ANGELINA_ID" : "192.168.0.102"
}

def main():

	# Make an object of the class you want to use and connect to the robot you want.

	# Select your robots ID from the IPs table
	ttx = ALProxy("ALTextToSpeech", IPs["LYRIA_ID"], 9559)

	# Use method say to run the program
	ttx.say("Hello Ali")

	print("test successful!")

if __name__ == "__main__":

	# Call the Main function.
	main()


# bracket = input()
bracket = input()
stack = []
cnt = 0
for i in range(len(bracket)):

    if bracket[i] == "(":
        stack.append(bracket[i])
    else:
        stack.pop()
        if bracket[i-1] == "(": # 레이저일때
            cnt += len(stack)
        else: # 쇠막대기의 끝
            cnt += 1

print(cnt)


# Our IDs have come from our order in the rotisserie draft.

john = {
	'id': 'john',
	'name': 'John'
}

graham = {
	'id': 'graham',
	'name': 'Graham'
}

mike = {
	'id': 'mike',
	'name': 'Mike'
}

adam = {
	'id': 'adam',
	'name': 'Adam'
}

martin = {
	'id': 'martin',
	'name': 'Martin'
}

players = [john, graham, mike, adam, martin]

players_dict = {
	p['id']:p for p in players
}


import cv2
import torch
import os
import numpy as np

def draw(img, heatmap, save_path):
    heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))
    heatmap = np.uint8(255 * heatmap)
    heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
    superimposed_img = heatmap*0.7 + img
    cv2.imwrite(save_path, superimposed_img)

def visualize_cam(img_dir, file_name, heatmap, save_dir, cats, is_split=False):
    if not os.path.exists(save_dir):
        os.makedirs(save_dir)
    img_path = os.path.join(img_dir, file_name)
    img = cv2.imread(img_path)
    if is_split:
        for cat, single_heatmap in zip(cats, heatmap):
            save_path = os.path.join(save_dir, '{}_{}.jpg').format(file_name[:-4], cat)
            draw(img, single_heatmap, save_path)
    else:
        save_path = os.path.join(save_dir, '{}').format(file_name)
        draw(img, heatmap, save_path)


import numpy as np
import os
import pickle
import meshio
import sys
sys.path.append("../")
from utils.stress_utils import *
from utils.constants import OBJECT_NAMES

dgn_dataset_path = "/home/baothach/stress_field_prediction/sim_data/stress_prediction_data/dgn_dataset"
adjacent_tetrahedrals_save_path = "/home/baothach/shape_servo_data/stress_field_prediction/adjacent_tetrahedrals"
data_recording_path = "/home/baothach/shape_servo_data/stress_field_prediction/data"
os.makedirs(adjacent_tetrahedrals_save_path, exist_ok=True)

for object_name in OBJECT_NAMES:
# for object_name in ["strawberry"]:
# for i, object_name in enumerate(sorted(os.listdir(dgn_dataset_path))):
    file_name = os.path.join(data_recording_path, f"{object_name}_grasp_{0}_force_{1.0}.pickle")
    if not os.path.isfile(file_name):
        print(f"{file_name} not found")
        break

    with open(file_name, 'rb') as handle:
        data = pickle.load(handle)
        (tet_indices, tet_stress) = data["tet"]
        tet_indices = np.array(tet_indices).reshape(-1,4)

    save_data = get_adjacent_tetrahedrals_of_vertex(tet_indices)
    with open(os.path.join(adjacent_tetrahedrals_save_path, f"{object_name}.pickle"), 'wb') as handle:
        pickle.dump(save_data, handle, protocol=pickle.HIGHEST_PROTOCOL)


import os
from enum import Enum
from typing import Optional, Any

from .utils.project_models import LogEvent, StepMetadata, Status, LogLevel

# class mimicking Either behaviour to capture both success and failure
class Either:

    def __init__(self, left=None, right=None):
        self.left = left
        self.right = right

    @property
    def is_left(self):
        return self.left is not None

    @property
    def is_right(self) -> bool:
        return self.right is not None

def custom_print(message: Optional[Any] = None, exception=None,
                 step_id=None,
                 step_metadata: Optional[StepMetadata] = None,
                 step_status: Optional[Status] = None, level: LogLevel = LogLevel.INFO):
    if is_online_mode():
        # Custom print: Print all variables.
        if step_metadata is not None:
            log_event = LogEvent.from_step_metadata(step_metadata)
        elif step_status is not None:
            log_event = LogEvent.from_status(step_status, step_id)
        else:
            log_event = LogEvent.from_log(step_id, message, exception)

        print(log_event.to_json(), flush=True)
    else:
        # Regular print: Skip stepName.
        if exception is not None:
            print(message, exception)
        else:
            if message is not None:
                print(message)

def is_online_mode() -> bool:
    return os.environ.get("PRINT_MODE", "REGULAR") == "CUSTOM"

# If the item is a dictionary

def remove_null_items_recursively(item):
    # If the item is a dictionary
    if isinstance(item, dict):
        return {
            k: remove_null_items_recursively(v)
            for k, v in item.items() if v is not None
        }

    # If the item is a list
    elif isinstance(item, list):
        return [remove_null_items_recursively(v) for v in item if v is not None]

    # If the item is an enum
    elif isinstance(item, Enum):
        return item.value

    # If the item is neither a dictionary, a list, nor an enum
    else:
        return item


"""
This is a set of utility functions for downloading and manipulating video/audio
"""
from pytube import YouTube
import skvideo.io  #requires 'brew install ffmpeg'
import torch
import librosa
import numpy as np
import os
from progressbar import ProgressBar
import json

def download_videos(URLs, DEST_PATH):
    """
    This function downloads the video given by URLs passed from YouTube.

    Params:
        -URLs: list of URLs to download
        -DEST_PATH: destination path for downloaded videos

    Returns:
        -Nothing
    """
    pbar = ProgressBar() # progress bar for downloading videos

    for url in pbar(URLs):
        try:
            yt = YouTube(url)
        except:
            print("Could not connect to YouTube...")

        # filter files for 'mp4' file type and get first file
        new_vid = yt.streams.filter(subtype='mp4').first()
        try:
            new_vid.download(DEST_PATH)
        except:
            print("Error downloading video...")

def video_to_array(VIDEO_FILE):
    """
    This function converts a mp4 file into a numpy array (takes a while)

    Params:
        -mp4 file

    Returns:
        -numpy array
    """
    return skvideo.io.vread(VIDEO_FILE)

def video_to_wave(VIDEO_FILE, timesteps):
    """
    This function returns a numpy array of sound bytes from a video
    """

def get_metadata(VIDEO_FILE):
    """
    This function returns a dict of the metadata for the mp4 file passed in.

    Params:
        -VIDEO_FILE: path to mp4 video

    Returns:
        -dict of metadata from file
    """
    return skvideo.io.ffprobe(video_location)

def print_metadata(metadata, mode='video'):
    """
    This function takes a dict of metadata and prints it to the terminal.

    Params:
        -metadata: dict of metadata to be printed
        -mode: type of metadata (either video or audio)

    Returns:
        -Nothing
    """
    if mode == 'video' or mode == 'audio':
        print("\n" + "---------- " + mode + " metadata ----------" + "\n")
        print(json.dumps(metadata[mode], indent=4))
        print("\n" + "---------- " + mode + " metadata ----------" + "\n")
    else:
        print("please enter valid mode (\"audio\" or \"video\")")

if __name__ == "__main__":

    destination = os.path.dirname(os.path.abspath(__file__))

    videos = ["https://www.youtube.com/watch?v=ToSe_CUG0F4"]

    # uncomment for downloading video
    # download_videos(videos, destination)

    video_location = "" # path to video
    print_metadata(get_metadata(video_location), "audio")

    #convert mp4 to array
    #video_array = video_to_array(video_location)
    #sound_bytes = get_sound_bytes(video_location)

    #print(type(video_array))
    #print(video_array.shape)


file = open('2018/Day 1/input.txt').readlines()
data = [int(x.strip()) for x in file]

print(f'The answer to part 1: {sum(data)}')

frequencies = set()

frequency = 0
i = 0
while True:
    if frequency in frequencies:
        print(f'The answer to part 2: {frequency}')
        break
    frequencies.add(frequency)

    frequency += data[i]
    i += 1
    i %= len(data)


# RNN Model 2 - LSTM
from keras.models import Model
from keras.layers import Input, LSTM

# Load SVG and other functions
from IPython.display import SVG
from keras.utils.vis_utils import model_to_dot

import os
os.environ["PATH"] += os.pathsep + 'C:/Users/Patrick/.conda/pkgs/graphviz-2.38.0-0/Library/bin/'

# Set unit, step, vector and data shape
units = 10
time_steps = 5
input_dim = 15
input_shape = (time_steps, input_dim)

# Create Recurrent Neural Network
x = Input(shape=input_shape, name='Input')
y = LSTM(units=units, activation='sigmoid', name='LSTM_1')(x)
model = Model(inputs=[x], outputs=[y])

# Use SVG to display
SVG(model_to_dot(model, show_shapes=True).create(prog='dot', format='svg'))

# Use Recurrent Neural Network model to ouput
y = LSTM(units=units, activation='sigmoid', return_sequences=True, name='LSTM_1')(x)
model = Model(inputs=[x], outputs=[y])

# Use SVG to display
SVG(model_to_dot(model, show_shapes=True).create(prog='dot', format='svg'))

# Create Recurrent Neural Network model and output status
x = Input(shape=input_shape, name='Input')
y, state_1, state_2 = LSTM(units=units, activation='sigmoid', return_state=True, name='LSTM_1')(x)
model = Model(inputs=[x], outputs=[y])

# Use SVG format to display
SVG(model_to_dot(model, show_shapes=True).create(prog='dot', format='svg'))

Basic for Deep Learning Book 1/RNN+Plots-LSTM.py


from Component_py.component import Component
from Component_py.stubs import require  # __:skip
React = require("react")

class MyComponent(Component):
    def __init__(self, *args) -> None:
        super().__init__(*args)
        self.state: Dict[str, int] = { "count": 0 }

    def render(self):
        return __pragma__("xtrans", None, "{}", """ (
            <div className="style">
                Hello World {self.state["count"]}
            </div>
        ); """)

    def componentDidMount(self) -> None:
        self.setState({ "count": 1 })
        console.log("Hello World")


#/usr/bin/env python3
# coding: utf-8

import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import argparse
import sys
import re

class PyplotConst (object):
	"""
	Constants for style-plotting with matplotlib.pyplot
	"""

	lineStyles = ['-', '--', '-.', ':', 'None', ' ', '']
	labelFont = {'fontname' : 'Linux Biolinum',
				 #~ 'weight' : 'bold',
				 'size'   : 14	}
	params = {'mathtext.default': 'regular' }

class Offsets (object):
	"""
	Defines the band offset of a single compound specified within the ALIGNMENTS file
	"""

	def __init__ (self, label, vbm, cbm):

		self.label = label
		"""
		Defines a label for the compound
		"""

		self.vbm = float(vbm)
		"""
		Defines the valence band maximum (ionization energy)
		"""

		self.cbm = float(cbm)
		"""
		Defines the conduction band minimum (electron affinity)
		"""

		self.vacum = 0
		"""
		Defines the vacuum level for this material
		"""

		self.color = 'black'
		"""
		Specifies the color for this band discontinuities
		"""

	def setVacuum (self, vac):
		"""
		Sets the vacuum potential for this material
		"""

		self.vacuum = float (vac)

	def setColor (self, color):
		"""
		Sets a color for this material representation
		"""

		self.color = color

def importAlignmentsList (fAlignments):
		'''
		The file ALIGNMENTS contains information on how to describe band offsets between the interfaces. Its format must be several lines organized as something like:

		Label   VBM    CBM   color

		Lines with --- and ... are band offsets dividers, representing dashed and dotted lines.

		For example:
		MoS$_2$ -5.795 -4.016 red
		--- red
		MoS$_2$ -5.819 -4.075 black
		SnS$_2$ -6.662 -5.120 black
		--- blue
		SnS$_2$ -6.586 -5.063 blue
		'''

		## Opens the ALIGNMENTS file
		try:
			with open(fAlignments,'r') as f:
				inputFile = f.read().strip()
				lines = [x for x in inputFile.split('\n') if x] # removes repeated \n ''

		except FileNotFoundError:
			print ('Alignments file not found. Please specify a valid filename.')
			sys.exit()

		offsetsList = []

		## Each line is a band offset or a ligature
		for eachLine in lines:
			## The first information should be the material
			offsetLabel = re.split(' +', eachLine.strip())[0]

			if offsetLabel in PyplotConst.lineStyles:
				## line format: LINE_STYLE COLOR
				try:
					color = re.split(' +', eachLine.strip())[1]
				except IndexError:
					print ('Invalid file formatting. Please correct it and try again.')
					sys.exit(1)

				## Creates an object which is only a ligature
				newOffset = Offsets (offsetLabel, 0, 0)
			else:
				## line format: LABEL VBM CBM COLOR
				try:
					vbm = re.split(' +', eachLine.strip())[1]
					cbm = re.split(' +', eachLine.strip())[2]
					color = re.split(' +', eachLine.strip())[3]
				except IndexError:
					print ('Invalid file formatting. Please correct it and try again.')
					sys.exit(1)

				## Creates an object which is a band offset
				newOffset = Offsets (offsetLabel, vbm, cbm)

			newOffset.setColor(color)
			offsetsList.append(newOffset)

		return offsetsList

def printOffsetsList (offsetsList, args):
		'''
		A list of band offsets is received and printed using matplotlib.pyplot.
		Lines joining different band offsets should not be at the end or beginning of the lists
		'''

		last_x = 0

		offset_x_length = 1
		offset_linewidth = 3
		ligature_x_length = 0.4
		ligature_linewidth = 1.5
		axes_margin = 0.25

		## Then, the offsets are plotted
		for i in range (len(offsetsList)):
			## Plot ligature
			if offsetsList[i].label in PyplotConst.lineStyles:

				## Reads the offsets from the last and the next one
				vbm_last = offsetsList[i-1].vbm
				cbm_last = offsetsList[i-1].cbm
				vbm_next = offsetsList[i+1].vbm
				cbm_next = offsetsList[i+1].cbm

				labelInterface = offsetsList[i-1].label + '/' + offsetsList[i+1].label

				if offsetsList[i].label == '-':
					## Plot line joining adjacent VBM
					plt.plot ([last_x, last_x], [vbm_last, vbm_next],
							  linestyle = offsetsList[i].label,
							  color = offsetsList[i].color,
							  linewidth = offset_linewidth)

					## Plot line joining adjacent CBM
					plt.plot ([last_x, last_x], [cbm_last, cbm_next],
							  linestyle = offsetsList[i].label,
							  color = offsetsList[i].color,
							  linewidth = offset_linewidth)

					## Label interface
					## label becomes aligned in the center if there is only one interface (two materials)
					if args.number == 2:
						plt.annotate (labelInterface, (last_x, (max(vbm_last, vbm_next)+ min(cbm_last, cbm_next))/2),
									  horizontalalignment='center', verticalalignment='center', **PyplotConst.labelFont)

					last_x = last_x

				else:
					## Plot line joining VBM with stylish lines
					plt.plot ([last_x, last_x + ligature_x_length], [vbm_last, vbm_next],
							  linestyle = offsetsList[i].label,
							  color = offsetsList[i].color,
							  linewidth = ligature_linewidth)

					## Plot line joining CBM with stylish lines
					plt.plot ([last_x, last_x + ligature_x_length], [cbm_last, cbm_next],
							  linestyle = offsetsList[i].label,
							  color = offsetsList[i].color,
							  linewidth = ligature_linewidth)

					last_x = last_x + ligature_x_length

			## Or plot offset
			else:

				vbm = offsetsList[i].vbm
				cbm = offsetsList[i].cbm

				## Plot VBM
				plt.plot ([last_x, last_x + offset_x_length], [vbm, vbm],
						  linestyle = '-',
						  color = offsetsList[i].color,
						  linewidth = offset_linewidth)

				plt.annotate (("% .2f" % offsetsList[i].vbm), (last_x + offset_x_length/2, vbm - 0.03), horizontalalignment='center', verticalalignment='top', **PyplotConst.labelFont)

				## Plot CBM
				plt.plot ([last_x, last_x + offset_x_length], [cbm, cbm],
						  linestyle = '-',
						  color = offsetsList[i].color,
						  linewidth = offset_linewidth)

				plt.annotate (("% .2f" % offsetsList[i].cbm), (last_x + offset_x_length/2, cbm + 0.02), horizontalalignment='center', verticalalignment='bottom', **PyplotConst.labelFont)

				plt.annotate (offsetsList[i].label, (last_x + offset_x_length/2, (vbm+cbm)/2),
									  horizontalalignment='center', verticalalignment='center', **PyplotConst.labelFont)

				last_x = last_x + offset_x_length

		## A set of pyplot configurations is passed
		plt.margins (axes_margin)
		plt.rcParams.update(PyplotConst.params)

		## Turn on the axis if requested
		if args.axis:
			plt.axis('on')
		else:
			plt.axis('off')

		plt.savefig (args.output)

		## Finally, the plot is shown
		if args.show:
			plt.show()

def parseArgs():
	"""
	Parse arguments from the command line. Uses the `argparse` package to
	establish all positional and optional arguments.
	"""
	parser = argparse.ArgumentParser(description='Easy script to create band alignments',
									epilog= "Written by Daniel S. Koda (feb. 2017).",
									prog="band_offsets.py")

	parser.add_argument('input_file', default='ALIGNMENTS', help="ALIGNMENTS input file")

	parser.add_argument('-o', '--output', default='offsets.png', help="output name for the generated files with its format. Default: offsets.png")

	parser.add_argument('-v', '--vacuum', type=float, default=0.0, help="vacuum dipole step (Default: 0.0)")

	parser.add_argument('-q', '--quiet', action='store_true', help="do not display text on the output window (default: False)")

	parser.add_argument('-x', '--axis', action='store_true', help="turn on the axis for the plot (default: False)")

	parser.add_argument('-s', '--show', action='store_true', help="show the band alignments before saving to file (default: False)")

	parser.add_argument('-n', '--number', type=int, default=2, help="number of materials composing the interface (Default: 2)")

	return parser.parse_args()

def printRunDescription (args):
	'''
	Print description of the options chosen and the crystals input.
	'''

	leftJustSpace = 20
	print ("input file:".ljust(leftJustSpace) + "%s" % args.input_file)
	print ("output file:".ljust(leftJustSpace) + "%s" % args.output)
	print ("vacuum step:".ljust(leftJustSpace) + "% .2f" % args.vacuum)

def main():
	'''
	Rotates a molecule in a POSCAR file by angleDegrees
	The refAtom is taken as reference to the rotation
	'''

	args = parseArgs()

	if not args.quiet:
		print ("*****************************")
		print (" vaspirin v2.0: band_offsets ")
		print ("*****************************")

		printRunDescription (args)

	## Open the input file and creates an offsets list
	offsetsList = importAlignmentsList (args.input_file)

	## Plots the artistic band offsets
	printOffsetsList (offsetsList, args)

if __name__ == "__main__":
	main ()


import torch
from collections import namedtuple

from absl import app

class CummuNoiseTorch:
    @torch.no_grad()
    def __init__(self, std, shapes, device, test_mode=False):
        """
        :param std: standard deviation of the noise
        :param shapes: shapes of the noise, which is basically shape of the gradients
        :param device: device for pytorch tensor
        :param test_mode: if in test mode, noise will be 1 in each node of the tree
        """
        assert std >= 0
        self.std = std
        self.shapes = shapes
        self.device = device
        self.step = 0
        self.binary = [0]
        self.noise_sum = [torch.zeros(shape).to(self.device) for shape in shapes]
        self.recorded = [[torch.zeros(shape).to(self.device) for shape in shapes]]
        self.test_mode = test_mode

    @torch.no_grad()
    def __call__(self):
        """
        :return: the noise to be added by DP-FTRL
        """
        if self.std <= 0 and not self.test_mode:
            return self.noise_sum

        self.step += 1

        idx = 0
        while idx < len(self.binary) and self.binary[idx] == 1:
            self.binary[idx] = 0
            for ns, re in zip(self.noise_sum, self.recorded[idx]):
                ns -= re
            idx += 1
        if idx >= len(self.binary):
            self.binary.append(0)
            self.recorded.append([torch.zeros(shape).to(self.device) for shape in self.shapes])

        for shape, ns, re in zip(self.shapes, self.noise_sum, self.recorded[idx]):
            if not self.test_mode:
                n = torch.normal(0, self.std, shape).to(self.device)
            else:
                n = torch.ones(shape).to(self.device)
            ns += n
            re.copy_(n)

        self.binary[idx] = 1
        return self.noise_sum

    @torch.no_grad()
    def proceed_until(self, step_target):
        """
        Proceed until the step_target-th step. This is for the binary tree completion trick.

        :return: the noise to be added by DP-FTRL
        """
        if self.step >= step_target:
            raise ValueError(f'Already reached {step_target}.')
        while self.step < step_target:
            noise_sum = self.__call__()
        return noise_sum

Element = namedtuple('Element', 'height value')

class CummuNoiseEffTorch:
    """
    The tree aggregation protocol with the trick in Honaker, "Efficient Use of Differentially Private Binary Trees", 2015
    """
    @torch.no_grad()
    def __init__(self, std, shapes, device):
        """
        :param std: standard deviation of the noise
        :param shapes: shapes of the noise, which is basically shape of the gradients
        :param device: device for pytorch tensor
        """
        self.std = std
        self.shapes = shapes
        self.device = device

        self.step = 0
        self.noise_sum = [torch.zeros(shape).to(self.device) for shape in shapes]
        self.stack = []

    @torch.no_grad()
    def get_noise(self):
        return [torch.normal(0, self.std, shape).to(self.device) for shape in self.shapes]

    @torch.no_grad()
    def push(self, elem):
        for i in range(len(self.shapes)):
            self.noise_sum[i] += elem.value[i] / (2.0 - 1 / 2 ** elem.height)
        self.stack.append(elem)

    @torch.no_grad()
    def pop(self):
        elem = self.stack.pop()
        for i in range(len(self.shapes)):
            self.noise_sum[i] -= elem.value[i] / (2.0 - 1 / 2 ** elem.height)

    @torch.no_grad()
    def __call__(self):
        """
        :return: the noise to be added by DP-FTRL
        """
        self.step += 1

        # add new element to the stack
        self.push(Element(0, self.get_noise()))

        # pop the stack
        while len(self.stack) >= 2 and self.stack[-1].height == self.stack[-2].height:
            # create new element
            left_value, right_value = self.stack[-2].value, self.stack[-1].value
            new_noise = self.get_noise()
            new_elem = Element(
                self.stack[-1].height + 1,
                [x + (y + z) / 2 for x, y, z in zip(new_noise, left_value, right_value)])

            # pop the stack, update sum
            self.pop()
            self.pop()

            # append to the stack, update sum
            self.push(new_elem)
        return self.noise_sum

    @torch.no_grad()
    def proceed_until(self, step_target):
        """
        Proceed until the step_target-th step. This is for the binary tree completion trick.

        :return: the noise to be added by DP-FTRL
        """
        if self.step >= step_target:
            raise ValueError(f'Already reached {step_target}.')
        while self.step < step_target:
            noise_sum = self.__call__()
        return noise_sum

def main(argv):
    # This is a small test. If we set the noise in each node as 1 (by setting
    # test_mode=True), we should be seeing the returned noise as the number of
    # 1s in the binary representations of i when cummu_noises is called i times.

    def countSetBits(n):
        count = 0
        while (n):
            n &= (n - 1)
            count += 1
        return count
    cummu_noises = CummuNoiseTorch(1.0, [(1,)], 'cuda', test_mode=True)
    for epoch in range(31):
        random_noise = cummu_noises()
        assert random_noise[0].cpu().numpy()[0] == countSetBits(epoch + 1)

if __name__ == '__main__':
    app.run(main)


import numpy as np
from enum import Enum

class SolutionCount(Enum):
    ZERO = 1
    ONE = 2
    INFINITY = 3

    def __str__(self):
        return self.name

# Gaussian is already taken :(
class ResultContainer:
    original_matrix: np.ndarray = None
    solution_count: SolutionCount = None
    triangle_matrix: np.ndarray = None
    triangles: np.ndarray = None
    determinant: float = None
    answer: np.ndarray = None
    answer_residual: np.ndarray = None  # English(невязка) == residual

def calculate_gauss_from_parameters(array: np.ndarray) -> ResultContainer:
    return _calculate(array)

def _calculate(array: np.ndarray) -> ResultContainer:
    result = ResultContainer()
    result.original_matrix = array
    result.solution_count = _kronecker_capelli(array)
    if result.solution_count != SolutionCount.ONE:
        return result
    result.triangle_matrix, result.triangles, switches = _forward_path(array.astype(np.float64))
    result.determinant = _calculate_determinant(result.triangle_matrix, switches)
    result.answer = _backward_path(result.triangle_matrix)
    result.answer_residual = _calculate_residual(result.original_matrix, result.answer)
    return result

def _kronecker_capelli(array: np.ndarray) -> SolutionCount:
    a_matrix = array[:, :-1]
    a_extended = array
    rank_a = np.linalg.matrix_rank(a_matrix)
    rank_a_ext = np.linalg.matrix_rank(a_extended)
    # rank(A) != rank(A|B)
    if rank_a != rank_a_ext:
        return SolutionCount.ZERO
    n = max(len(array), len(array[0]) - 1)
    # rank(A) == rank(A|B) < n
    if rank_a < n:
        return SolutionCount.INFINITY
    # rank(A) == rank(A|B) == n
    else:
        return SolutionCount.ONE

def _forward_path(array: np.ndarray) -> [np.ndarray, np.ndarray, int]:
    n = len(array)
    triangles = []
    switches = 0
    for i in range(n-1):
        max_val = abs(array[i][i])
        max_row = i
        for j in range(i + 1, n):
            if abs(array[j][i]) > max_val:
                max_val = abs(array[j][i])
                max_row = j
        if i != max_row:
            array[i], array[max_row] = list(array[max_row]), list(array[i])
            switches += 1

        a_ii = array[i][i]
        for j in range(i + 1, n):
            a_ji = array[j][i]
            r = - a_ji / a_ii
            for k in range(i, n + 1):
                array[j][k] += array[i][k] * r

        triangles.append(array.copy())

    return [array, triangles, switches]

def _calculate_determinant(triangle_matrix: np.ndarray, switches: int) -> float:
    n = len(triangle_matrix)
    determinant = (-1.0) ** switches
    for i in range(n):
        determinant *= triangle_matrix[i][i]
    return determinant

def _backward_path(array: np.ndarray) -> np.ndarray:
    n = len(array)
    x = list(0.0 for _ in range(n))
    for i in range(n - 1, -1, -1):
        a_ii = array[i][i]
        b = array[i][n]
        for j in range(i + 1, n):
            b -= array[i][j] * x[j]
        x[i] = b / a_ii
    return np.array(x)

def _calculate_residual(matrix: np.ndarray, answer: np.ndarray) -> np.ndarray:
    residuals = list()
    n = len(matrix)
    for i in range(n):
        residual = -matrix[i][n]
        for j in range(n):
            residual += matrix[i][j] * answer[j]
        residuals.append(residual)
    return np.array(residuals)


import cv2
import math
import time
import numpy as np

from AccessMath.interface.controls.screen import Screen
from AccessMath.interface.controls.screen_label import ScreenLabel
from AccessMath.interface.controls.screen_button import ScreenButton
from AccessMath.interface.controls.screen_image import ScreenImage
from AccessMath.interface.controls.screen_container import ScreenContainer
from AccessMath.interface.controls.screen_horizontal_scroll import ScreenHorizontalScroll

import scipy.ndimage.measurements as sci_mes
from AccessMath.preprocessing.content.labeler import Labeler

class GTPixelBinaryAnnotator(Screen):
    ModeNavigation = 0
    ModeEdition = 1
    ModeConfirmCancel = 2
    ModeGrowCC_Select = 3
    ModeGrowCC_Growing = 4
    ModeShrinkCC_Select = 5
    ModeShrinkCC_Shrinking = 6

    ViewModeRaw = 0
    ViewModeGray = 1
    ViewModeBinary = 2
    ViewModeSoftCombined = 3
    ViewModeHardCombined = 4

    CCShowBlack = 0
    CCShowColored = 1
    CCShowColors = [(255, 0, 0), (0, 255, 0), (0, 0, 255),
                    (255, 255, 0), (255, 0, 255), (0, 255, 255),
                    (128, 0, 0), (0, 128, 0), (0, 0, 128),
                    (128, 128, 0), (128, 0, 128), (0, 128, 128),
                    (255, 128, 0), (255, 0, 128), (0, 255, 128),
                    (128, 255, 0), (128, 0, 255), (0, 128, 255),]

    CCExpansionDistance = 1

    def __init__(self, size, raw_input, binary_input, parent_screen=None):
        Screen.__init__(self, "Ground Truth Binary Pixel Annotation Interface", size)

        self.small_mode = self.height < 800

        # base images
        self.base_raw = raw_input.copy()
        tempo_gray = cv2.cvtColor(raw_input, cv2.COLOR_RGB2GRAY)
        self.base_gray = np.zeros((self.base_raw.shape[0], self.base_raw.shape[1], 3), dtype=np.uint8)
        self.base_gray[:, :, 0] = tempo_gray.copy()
        self.base_gray[:, :, 1] = tempo_gray.copy()
        self.base_gray[:, :, 2] = tempo_gray.copy()

        self.base_binary = binary_input.copy()

        self.base_ccs = None
        self.base_cc_image = None    # colored version

        # stats
        self.stats_pixels_white = 0
        self.stats_pixels_black = 0
        self.stats_pixels_ratio = 0

        self.stats_cc_count = 0
        self.stats_cc_size_min = 0
        self.stats_cc_size_max = 0
        self.stats_cc_size_mean = 0
        self.stats_cc_size_median = 0

        # for automatic CC expansion
        self.selected_CC = None
        self.CC_expansion_pixels = None
        self.count_expand = 0
        # ... shrinking ...
        self.CC_shrinking_pixels = None
        self.count_shrink = 0

        # view params
        self.view_scale = 1.0
        self.max_scale = None
        self.view_mode = GTPixelBinaryAnnotator.ViewModeBinary
        self.editor_mode = GTPixelBinaryAnnotator.ModeNavigation
        self.cc_show_mode = GTPixelBinaryAnnotator.CCShowColored
        self.min_highlight_size = 0

        # appearance parameters
        general_background = (125, 40, 20)
        text_color = (255, 255, 255)
        button_text_color = (50, 35, 20)
        button_back_color = (228, 228, 228)
        self.elements.back_color = general_background

        # add elements....
        # right panel button size and horizontal locations
        container_width = 330

        button_width = 190
        button_left = (container_width - button_width) / 2

        button_2_width = 150
        button_2_left = int(container_width * 0.25) - button_2_width / 2
        button_2_right = int(container_width * 0.75) - button_2_width / 2

        button_3_width = 100
        button_3_left = 10
        button_3_middle = (container_width - button_3_width) / 2
        button_3_right = container_width - button_3_width - 10

        button_4_width = 75
        button_4_left_1 = int(container_width * 0.125) - button_4_width / 2
        button_4_left_2 = int(container_width * 0.375) - button_4_width / 2
        button_4_left_3 = int(container_width * 0.625) - button_4_width / 2
        button_4_left_4 = int(container_width * 0.875) - button_4_width / 2

        # View panel with Zoom control buttons
        self.container_zoom_buttons = ScreenContainer("container_zoom_buttons", (container_width, 80),
                                                      back_color=general_background)
        self.container_zoom_buttons.position = (self.width - container_width - 10, 10)
        self.elements.append(self.container_zoom_buttons)

        # zoom ....
        self.lbl_zoom = ScreenLabel("lbl_zoom", "Zoom: 100%", 21, container_width - 10, 1)
        self.lbl_zoom.position = (5, 5)
        self.lbl_zoom.set_background(general_background)
        self.lbl_zoom.set_color(text_color)
        self.container_zoom_buttons.append(self.lbl_zoom)

        self.btn_zoom_reduce = ScreenButton("btn_zoom_reduce", "[ - ]", 21, 90)
        self.btn_zoom_reduce.set_colors(button_text_color, button_back_color)
        self.btn_zoom_reduce.position = (10, self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_reduce.click_callback = self.btn_zoom_reduce_click
        self.container_zoom_buttons.append(self.btn_zoom_reduce)

        self.btn_zoom_increase = ScreenButton("btn_zoom_increase", "[ + ]", 21, 90)
        self.btn_zoom_increase.set_colors(button_text_color, button_back_color)
        self.btn_zoom_increase.position = (self.container_zoom_buttons.width - self.btn_zoom_increase.width - 10,
                                           self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_increase.click_callback = self.btn_zoom_increase_click
        self.container_zoom_buttons.append(self.btn_zoom_increase)

        self.btn_zoom_zero = ScreenButton("btn_zoom_zero", "100%", 21, 90)
        self.btn_zoom_zero.set_colors(button_text_color, button_back_color)
        self.btn_zoom_zero.position = ((self.container_zoom_buttons.width - self.btn_zoom_zero.width) / 2,
                                       self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_zero.click_callback = self.btn_zoom_zero_click
        self.container_zoom_buttons.append(self.btn_zoom_zero)

        # ===========================
        self.container_cc_expansion = ScreenContainer("container_cc_expansion", (container_width, 150), back_color=general_background)
        self.container_cc_expansion.position = (self.container_zoom_buttons.get_left(), self.container_zoom_buttons.get_bottom() + 10)
        self.elements.append(self.container_cc_expansion)

        self.lbl_cc_expansion = ScreenLabel("lbl_cc_expansion", "Expand CC, Pixels = " + str(self.count_expand), 21, container_width - 10, 1)
        self.lbl_cc_expansion.position = (5, 5)
        self.lbl_cc_expansion.set_background(general_background)
        self.lbl_cc_expansion.set_color(text_color)
        self.container_cc_expansion.append(self.lbl_cc_expansion)

        self.expansion_scroll = ScreenHorizontalScroll("expansion_scroll", 0, 100, 0, 10)
        self.expansion_scroll.position = (5, self.lbl_cc_expansion.get_bottom() + 10)
        self.expansion_scroll.width = container_width - 10
        self.expansion_scroll.scroll_callback = self.expansion_scroll_change
        self.container_cc_expansion.append(self.expansion_scroll)

        self.lbl_expand_confirm = ScreenLabel("lbl_confirm", "Expand CC Pixels", 21, container_width - 10, 1)
        self.lbl_expand_confirm.position = (5, self.expansion_scroll.get_bottom() + 15)
        self.lbl_expand_confirm.set_background(general_background)
        self.lbl_expand_confirm.set_color(text_color)
        self.container_cc_expansion.append(self.lbl_expand_confirm)

        self.btn_expand_accept = ScreenButton("btn_expand_accept", "Accept", 21, 130)
        self.btn_expand_accept.set_colors(button_text_color, button_back_color)
        self.btn_expand_accept.position = (10, self.lbl_expand_confirm.get_bottom() + 10)
        self.btn_expand_accept.click_callback = self.btn_expand_accept_click
        self.container_cc_expansion.append(self.btn_expand_accept)

        self.btn_expand_cancel = ScreenButton("btn_expand_cancel", "Cancel", 21, 130)
        self.btn_expand_cancel.set_colors(button_text_color, button_back_color)
        self.btn_expand_cancel.position = (container_width - self.btn_expand_cancel.width - 10, self.lbl_expand_confirm.get_bottom() + 10)
        self.btn_expand_cancel.click_callback = self.btn_expand_cancel_click
        self.container_cc_expansion.append(self.btn_expand_cancel)
        self.container_cc_expansion.visible = False

        # =======================================

        # View panel with view control buttons
        self.container_view_buttons = ScreenContainer("container_view_buttons", (container_width, 200),
                                                      back_color=general_background)
        self.container_view_buttons.position = (self.width - container_width - 10, self.container_zoom_buttons.get_bottom() + 5)
        self.elements.append(self.container_view_buttons)

        # ===========================
        self.lbl_views = ScreenLabel("lbl_zoom", "Views", 21, button_3_width, 1)
        self.lbl_views.position = (button_3_left, 5)
        self.lbl_views.set_background(general_background)
        self.lbl_views.set_color(text_color)
        self.container_view_buttons.append(self.lbl_views)

        self.btn_view_raw = ScreenButton("btn_view_raw", "Raw", 21, button_3_width)
        self.btn_view_raw.set_colors(button_text_color, button_back_color)
        self.btn_view_raw.position = (button_3_middle, 5)
        self.btn_view_raw.click_callback = self.btn_view_raw_click
        self.container_view_buttons.append(self.btn_view_raw)

        self.btn_view_gray = ScreenButton("btn_view_gray", "Gray", 21, button_3_width)
        self.btn_view_gray.set_colors(button_text_color, button_back_color)
        self.btn_view_gray.position = (button_3_right, 5)
        self.btn_view_gray.click_callback = self.btn_view_gray_click
        self.container_view_buttons.append(self.btn_view_gray)

        self.btn_view_binary = ScreenButton("btn_view_binary", "Binary", 21, button_3_width)
        self.btn_view_binary.set_colors(button_text_color, button_back_color)
        self.btn_view_binary.position = (button_3_left, self.btn_view_gray.get_bottom() + 10)
        self.btn_view_binary.click_callback = self.btn_view_bin_click
        self.container_view_buttons.append(self.btn_view_binary)

        self.btn_view_combo_hard = ScreenButton("btn_view_combo_hard", "CC Hard", 21, button_3_width)
        self.btn_view_combo_hard.set_colors(button_text_color, button_back_color)
        self.btn_view_combo_hard.position = (button_3_middle, self.btn_view_gray.get_bottom() + 10)
        self.btn_view_combo_hard.click_callback = self.btn_view_combo_hard_click
        self.container_view_buttons.append(self.btn_view_combo_hard)

        self.btn_view_combo_soft = ScreenButton("btn_view_combo_soft", "CC Soft", 21, button_3_width)
        self.btn_view_combo_soft.set_colors(button_text_color, button_back_color)
        self.btn_view_combo_soft.position = (button_3_right, self.btn_view_gray.get_bottom() + 10)
        self.btn_view_combo_soft.click_callback = self.btn_view_combo_soft_click
        self.container_view_buttons.append(self.btn_view_combo_soft)

        # ===========================
        self.lbl_show_cc = ScreenLabel("lbl_show_cc", "Display CC", 21, button_3_width, 1)
        self.lbl_show_cc.position = (button_3_left, self.btn_view_combo_soft.get_bottom() + 20)
        self.lbl_show_cc.set_background(general_background)
        self.lbl_show_cc.set_color(text_color)
        self.container_view_buttons.append(self.lbl_show_cc)

        self.btn_show_cc_black = ScreenButton("btn_show_cc_black", "Black", 21, button_3_width)
        self.btn_show_cc_black.set_colors(button_text_color, button_back_color)
        self.btn_show_cc_black.position = (button_3_middle, self.btn_view_combo_soft.get_bottom() + 10)
        self.btn_show_cc_black.click_callback = self.btn_show_cc_black_click
        self.container_view_buttons.append(self.btn_show_cc_black)

        self.btn_show_cc_colored = ScreenButton("btn_show_cc_colored", "Colored", 21, button_3_width)
        self.btn_show_cc_colored.set_colors(button_text_color, button_back_color)
        self.btn_show_cc_colored.position = (button_3_right, self.btn_view_combo_soft.get_bottom() + 10)
        self.btn_show_cc_colored.click_callback = self.btn_show_cc_colored_click
        self.container_view_buttons.append(self.btn_show_cc_colored)

        # ===========================
        self.lbl_small_highlight = ScreenLabel("lbl_small_highlight", "Highlight CCs smaller than: " + str(self.min_highlight_size), 21, 290, 1)
        self.lbl_small_highlight.position = (5, self.btn_show_cc_black.get_bottom() + 20)
        self.lbl_small_highlight.set_background(general_background)
        self.lbl_small_highlight.set_color(text_color)
        self.container_view_buttons.append(self.lbl_small_highlight)

        self.highlight_scroll = ScreenHorizontalScroll("highlight_scroll", 0, 100, 0, 10)
        self.highlight_scroll.position = (5, self.lbl_small_highlight.get_bottom() + 10)
        self.highlight_scroll.width = container_width - 10
        self.highlight_scroll.scroll_callback = self.highlight_scroll_change
        self.container_view_buttons.append(self.highlight_scroll)

        # ===========================
        self.container_edition_mode = ScreenContainer("container_edition_mode", (container_width, 60),
                                                      back_color=general_background)
        self.container_edition_mode.position = (self.width - container_width - 10, self.container_view_buttons.get_bottom() + 5)
        self.elements.append(self.container_edition_mode)

        self.btn_edition_start = ScreenButton("btn_edition_start", "Edit Pixels", 21, button_3_width)
        self.btn_edition_start.set_colors(button_text_color, button_back_color)
        self.btn_edition_start.position = (button_3_left, 5)
        self.btn_edition_start.click_callback = self.btn_edition_start_click
        self.container_edition_mode.append(self.btn_edition_start)

        self.btn_edition_stop = ScreenButton("btn_edition_stop", "Stop Editing Pixels", 21, button_width)
        self.btn_edition_stop.set_colors(button_text_color, button_back_color)
        self.btn_edition_stop.position = (button_left, 5)
        self.btn_edition_stop.click_callback = self.btn_edition_stop_click
        self.btn_edition_stop.visible = False
        self.container_edition_mode.append(self.btn_edition_stop)

        self.btn_edition_expand = ScreenButton("btn_edition_expand", "Expand CC", 21, button_3_width)
        self.btn_edition_expand.set_colors(button_text_color, button_back_color)
        self.btn_edition_expand.position = (button_3_middle, 5)
        self.btn_edition_expand.click_callback = self.btn_edition_expand_click
        self.container_edition_mode.append(self.btn_edition_expand)

        self.btn_edition_shrink = ScreenButton("btn_edition_shrink", "Shrink\nCC", 21, button_3_width)
        self.btn_edition_shrink.set_colors(button_text_color, button_back_color)
        self.btn_edition_shrink.position = (button_3_right, 5)
        self.btn_edition_shrink.click_callback = self.btn_edition_shrink_click
        self.container_edition_mode.append(self.btn_edition_shrink)

        # ===========================
        # Panel with confirmation buttons (Message, Accept, Cancel)
        self.container_confirm_buttons = ScreenContainer("container_confirm_buttons", (300, 70), back_color=general_background)
        self.container_confirm_buttons.position = (self.container_view_buttons.get_left(), self.container_edition_mode.get_bottom() + 10)
        self.elements.append(self.container_confirm_buttons)

        self.lbl_confirm = ScreenLabel("lbl_confirm", "Exit without saving?", 21, 290, 1)
        self.lbl_confirm.position = (5, 5)
        self.lbl_confirm.set_background(general_background)
        self.lbl_confirm.set_color(text_color)
        self.container_confirm_buttons.append(self.lbl_confirm)

        self.btn_confirm_accept = ScreenButton("btn_confirm_accept", "Accept", 21, 130)
        self.btn_confirm_accept.set_colors(button_text_color, button_back_color)
        self.btn_confirm_accept.position = (10, self.lbl_confirm.get_bottom() + 10)
        self.btn_confirm_accept.click_callback = self.btn_confirm_accept_click
        self.container_confirm_buttons.append(self.btn_confirm_accept)

        self.btn_confirm_cancel = ScreenButton("btn_confirm_cancel", "Cancel", 21, 130)
        self.btn_confirm_cancel.set_colors(button_text_color, button_back_color)
        self.btn_confirm_cancel.position = (self.container_confirm_buttons.width - self.btn_confirm_cancel.width - 10,
                                            self.lbl_confirm.get_bottom() + 10)
        self.btn_confirm_cancel.click_callback = self.btn_confirm_cancel_click
        self.container_confirm_buttons.append(self.btn_confirm_cancel)
        self.container_confirm_buttons.visible = False

        # =============================
        stats_background = (60, 20, 10)
        self.container_stats = ScreenContainer("container_stats", (container_width, 160), back_color=stats_background)
        self.container_stats.position = (self.width - container_width - 10, self.container_edition_mode.get_bottom() + 5)
        self.elements.append(self.container_stats)

        self.lbl_pixel_stats = ScreenLabel("lbl_pixel_stats", "Pixel Stats", 21, container_width - 10, 1)
        self.lbl_pixel_stats.position = (5, 5)
        self.lbl_pixel_stats.set_background(stats_background)
        self.lbl_pixel_stats.set_color(text_color)
        self.container_stats.append(self.lbl_pixel_stats)

        self.lbl_pixels_white = ScreenLabel("lbl_pixels_white", "White:\n1000000", 21, button_3_width, 1)
        self.lbl_pixels_white.position = (button_3_left, self.lbl_pixel_stats.get_bottom() + 10)
        self.lbl_pixels_white.set_background(stats_background)
        self.lbl_pixels_white.set_color(text_color)
        self.container_stats.append(self.lbl_pixels_white)

        self.lbl_pixels_black = ScreenLabel("lbl_pixels_black", "Black:\n1000000", 21, button_3_width, 1)
        self.lbl_pixels_black.position = (button_3_middle, self.lbl_pixel_stats.get_bottom() + 10)
        self.lbl_pixels_black.set_background(stats_background)
        self.lbl_pixels_black.set_color(text_color)
        self.container_stats.append(self.lbl_pixels_black)

        self.lbl_pixels_ratio = ScreenLabel("lbl_pixels_ratio", "Ratio:\n0.00000", 21, button_3_width, 1)
        self.lbl_pixels_ratio.position = (button_3_right, self.lbl_pixel_stats.get_bottom() + 10)
        self.lbl_pixels_ratio.set_background(stats_background)
        self.lbl_pixels_ratio.set_color(text_color)
        self.container_stats.append(self.lbl_pixels_ratio)

        self.lbl_cc_stats = ScreenLabel("lbl_cc_stats", "CC Stats", 21, container_width - 10, 1)
        self.lbl_cc_stats.position = (5, self.lbl_pixels_ratio.get_bottom() + 10)
        self.lbl_cc_stats.set_background(stats_background)
        self.lbl_cc_stats.set_color(text_color)
        self.container_stats.append(self.lbl_cc_stats)

        self.lbl_cc_stats_count = ScreenLabel("lbl_cc_stats_count", "Total CC: 0", 21, container_width - 10, 1)
        self.lbl_cc_stats_count.position = (5, self.lbl_cc_stats.get_bottom() + 10)
        self.lbl_cc_stats_count.set_background(stats_background)
        self.lbl_cc_stats_count.set_color(text_color)
        self.container_stats.append(self.lbl_cc_stats_count)

        self.lbl_cc_stats_min = ScreenLabel("lbl_cc_stats_min", "Min:\n0000", 21, button_4_width, 1)
        self.lbl_cc_stats_min.position = (button_4_left_1, self.lbl_cc_stats_count.get_bottom() + 10)
        self.lbl_cc_stats_min.set_background(stats_background)
        self.lbl_cc_stats_min.set_color(text_color)
        self.container_stats.append(self.lbl_cc_stats_min)

        self.lbl_cc_stats_max = ScreenLabel("lbl_cc_stats_max", "Max:\n0000", 21, button_4_width, 1)
        self.lbl_cc_stats_max.position = (button_4_left_2, self.lbl_cc_stats_count.get_bottom() + 10)
        self.lbl_cc_stats_max.set_background(stats_background)
        self.lbl_cc_stats_max.set_color(text_color)
        self.container_stats.append(self.lbl_cc_stats_max)

        self.lbl_cc_stats_mean = ScreenLabel("lbl_cc_stats_mean", "Mean:\n0000", 21, button_4_width, 1)
        self.lbl_cc_stats_mean.position = (button_4_left_3, self.lbl_cc_stats_count.get_bottom() + 10)
        self.lbl_cc_stats_mean.set_background(stats_background)
        self.lbl_cc_stats_mean.set_color(text_color)
        self.container_stats.append(self.lbl_cc_stats_mean)

        self.lbl_cc_stats_median = ScreenLabel("lbl_cc_stats_median", "Median:\n0000", 21, button_4_width, 1)
        self.lbl_cc_stats_median.position = (button_4_left_4, self.lbl_cc_stats_count.get_bottom() + 10)
        self.lbl_cc_stats_median.set_background(stats_background)
        self.lbl_cc_stats_median.set_color(text_color)
        self.container_stats.append(self.lbl_cc_stats_median)

        # ============================================================================
        # Panel with state buttons (Undo, Redo, return accept, return cancel)
        self.container_state_buttons = ScreenContainer("container_state_buttons", (container_width, 120), general_background)
        self.container_state_buttons.position = (self.container_stats.get_left(), self.container_stats.get_bottom() + 10)
        self.elements.append(self.container_state_buttons)

        self.btn_undo = ScreenButton("btn_undo", "Undo", 21, button_2_width)
        self.btn_undo.set_colors(button_text_color, button_back_color)
        self.btn_undo.position = (button_2_left, 5)
        self.btn_undo.click_callback = self.btn_undo_click
        self.container_state_buttons.append(self.btn_undo)

        self.btn_redo = ScreenButton("btn_redo", "Redo", 21, button_2_width)
        self.btn_redo.set_colors(button_text_color, button_back_color)
        self.btn_redo.position = (button_2_right, 5)
        self.btn_redo.click_callback = self.btn_redo_click
        self.container_state_buttons.append(self.btn_redo)

        # Secondary screen mode
        # Add Cancel Button
        self.btn_return_cancel = ScreenButton("btn_return_cancel", "Cancel", 21, button_2_width)
        self.btn_return_cancel.set_colors(button_text_color, button_back_color)
        self.btn_return_cancel.position = (button_2_left, self.btn_redo.get_bottom() + 30)
        self.btn_return_cancel.click_callback = self.btn_return_cancel_click
        self.container_state_buttons.append(self.btn_return_cancel)

        # Add Accept Button
        self.btn_return_accept = ScreenButton("btn_return_accept", "Accept", 21, button_2_width)
        self.btn_return_accept.set_colors(button_text_color, button_back_color)
        self.btn_return_accept.position = (button_2_right, self.btn_redo.get_bottom() + 30)
        self.btn_return_accept.click_callback = self.btn_return_accept_click
        self.container_state_buttons.append(self.btn_return_accept)

        # ============================================================================
        image_width = self.width - self.container_view_buttons.width - 30
        image_height = self.height - 20
        self.container_images = ScreenContainer("container_images", (image_width, image_height), back_color=(0, 0, 0))
        self.container_images.position = (10, 10)
        self.elements.append(self.container_images)

        # ... image objects ...
        tempo_blank = np.zeros((50, 50, 3), np.uint8)
        tempo_blank[:, :, :] = 255
        self.img_main = ScreenImage("img_raw", tempo_blank, 0, 0, True, cv2.INTER_NEAREST)
        self.img_main.position = (0, 0)
        self.img_main.mouse_button_down_callback = self.img_mouse_down
        self.img_main.mouse_motion_callback = self.img_mouse_motion
        self.container_images.append(self.img_main)

        self.pre_edition_binary = None
        self.undo_stack = []
        self.redo_stack = []

        self.finished_callback = None
        self.parent_screen = parent_screen

        self.last_motion_set_x = None
        self.last_motion_set_y = None
        self.last_motion_polarity = None

        self.update_cc_info()
        self.update_current_view(True)

    def update_current_view(self, resized=False, region=None):
        if region is None:
            if self.view_mode == GTPixelBinaryAnnotator.ViewModeGray:
                base_image = self.base_gray
            elif self.view_mode == GTPixelBinaryAnnotator.ViewModeBinary:
                if self.cc_show_mode == GTPixelBinaryAnnotator.CCShowBlack:
                    base_image = np.zeros((self.base_binary.shape[0], self.base_binary.shape[1], 3), dtype=np.uint8)

                    base_image[:, :, 0] = self.base_binary
                    base_image[:, :, 1] = self.base_binary
                    base_image[:, :, 2] = self.base_binary
                else:
                    base_image = self.base_cc_image.copy()
                    base_image[self.base_binary > 0, :] = 255

            elif self.view_mode == GTPixelBinaryAnnotator.ViewModeHardCombined:
                base_image = self.base_gray.copy()

                # create hard combined
                inverse_binary_mask = self.base_binary == 0
                if self.cc_show_mode == GTPixelBinaryAnnotator.CCShowBlack:
                    base_image[inverse_binary_mask, :] = 0
                else:
                    base_image[inverse_binary_mask, :] = self.base_cc_image[inverse_binary_mask, :].copy()

            elif self.view_mode == GTPixelBinaryAnnotator.ViewModeSoftCombined:
                if self.cc_show_mode == GTPixelBinaryAnnotator.CCShowBlack:
                    base_image = np.zeros((self.base_binary.shape[0], self.base_binary.shape[1], 3), dtype=np.uint8)

                    scaled_black = 1 - (self.base_binary / 255)
                    base_image[:, :, 0] = scaled_black
                else:
                    base_image = self.base_cc_image.copy()

                # replace the empty channels on each CC with original CC grayscale
                for channel in range(3):
                    inverse_binary_mask = base_image[:, :, channel] == 0

                    base_image[inverse_binary_mask, channel] = self.base_gray[inverse_binary_mask, 0].copy()
            else:
                base_image = self.base_raw.copy()
        else:
            start_x = max(region[0], 0)
            end_x = min(region[1] + 1, self.base_raw.shape[1])
            start_y = max(region[2], 0)
            end_y = min(region[3] + 1, self.base_raw.shape[0])

            # create empty image
            base_image = np.zeros((self.base_binary.shape[0], self.base_binary.shape[1], 3), dtype=np.uint8)

            if self.view_mode == GTPixelBinaryAnnotator.ViewModeGray:
                base_image[start_y:end_y, start_x:end_x] = self.base_gray[start_y:end_y, start_x:end_x].copy()

            elif self.view_mode == GTPixelBinaryAnnotator.ViewModeBinary:
                if self.cc_show_mode == GTPixelBinaryAnnotator.CCShowBlack:

                    base_image[start_y:end_y, start_x:end_x, 0] = self.base_binary[start_y:end_y, start_x:end_x].copy()
                    base_image[start_y:end_y, start_x:end_x, 1] = self.base_binary[start_y:end_y, start_x:end_x].copy()
                    base_image[start_y:end_y, start_x:end_x, 2] = self.base_binary[start_y:end_y, start_x:end_x].copy()
                else:
                    base_image[start_y:end_y, start_x:end_x] = self.base_cc_image[start_y:end_y, start_x:end_x].copy()

                    cut_mask = self.base_binary[start_y:end_y, start_x:end_x] > 0
                    tempo_cut = base_image[start_y:end_y, start_x:end_x]
                    tempo_cut[cut_mask, :] = 255

            elif self.view_mode == GTPixelBinaryAnnotator.ViewModeHardCombined:
                base_image[start_y:end_y, start_x:end_x] = self.base_gray[start_y:end_y, start_x:end_x].copy()

                # create hard combined
                inverse_binary_mask = self.base_binary[start_y:end_y, start_x:end_x] == 0
                tempo_cut = base_image[start_y:end_y, start_x:end_x]

                if self.cc_show_mode == GTPixelBinaryAnnotator.CCShowBlack:
                    tempo_cut[inverse_binary_mask, :] = 0
                else:
                    cc_image_cut = self.base_cc_image[start_y:end_y, start_x:end_x].copy()
                    tempo_cut[inverse_binary_mask, :] = cc_image_cut[inverse_binary_mask, :]

            elif self.view_mode == GTPixelBinaryAnnotator.ViewModeSoftCombined:
                if self.cc_show_mode == GTPixelBinaryAnnotator.CCShowBlack:

                    scaled_black = 1 - (self.base_binary[start_y:end_y, start_x:end_x] / 255)
                    base_image[start_y:end_y, start_x:end_x, 0] = scaled_black
                else:
                    base_image[start_y:end_y, start_x:end_x] = self.base_cc_image[start_y:end_y, start_x:end_x].copy()

                # replace the empty channels on each CC with original CC grayscale
                base_cut = base_image[start_y:end_y, start_x:end_x, :]
                gray_cut = self.base_gray[start_y:end_y, start_x:end_x, 0].copy()
                for channel in range(3):
                    inverse_binary_mask = base_image[start_y:end_y, start_x:end_x, channel] == 0

                    base_cut[inverse_binary_mask, channel] = gray_cut[inverse_binary_mask].copy()
                    #base_image[inverse_binary_mask, channel] = self.base_gray[inverse_binary_mask, 0].copy()
            else:
                base_image[start_y:end_y, start_x:end_x] = self.base_raw[start_y:end_y, start_x:end_x].copy()

        h, w, c = base_image.shape

        modified_image = base_image.copy()

        if self.selected_CC is not None:
            # mark selected CC ...
            cc = self.selected_CC
            current_cut = modified_image[cc.min_y:cc.max_y + 1, cc.min_x:cc.max_x + 1]
            cc_mask = cc.img > 0
            current_cut[cc_mask, 0] = 255

            # Highlight pixels for current threshold
            if self.editor_mode == GTPixelBinaryAnnotator.ModeGrowCC_Growing:
                for value, y, x in self.CC_expansion_pixels[:self.count_expand]:
                    modified_image[y, x, 1] = 1
            elif self.editor_mode == GTPixelBinaryAnnotator.ModeShrinkCC_Shrinking:
                for value, y, x in self.CC_shrinking_pixels[:self.count_shrink]:
                    modified_image[y, x, 1] = 1

        # show highlighted small CC (if any)
        if self.min_highlight_size > 0:
            for cc in self.base_ccs:
                if cc.size < self.min_highlight_size:
                    # print(str((cc.getCenter(), cc.size, cc.min_x, cc.max_x, cc.min_y, cc.max_y)))
                    # compute highlight base radius
                    base_radius = math.sqrt(math.pow(cc.getWidth() / 2, 2.0) + math.pow(cc.getHeight() / 2, 2.0))
                    highlight_radius = int(base_radius * 3)

                    cc_cx, cc_cy = cc.getCenter()
                    cv2.circle(modified_image, (int(cc_cx), int(cc_cy)), highlight_radius, (255, 0, 0), 2)

        if region is None:
            # resize ...
            new_res = (int(w * self.view_scale), int(h * self.view_scale))
            modified_image = cv2.resize(modified_image, new_res, interpolation=cv2.INTER_NEAREST)

            # add grid
            if self.view_scale >= 4.0:
                int_scale = int(self.view_scale)
                density = 2
                modified_image[int_scale - 1::int_scale, ::density, :] = 128
                modified_image[::density, int_scale - 1::int_scale, :] = 128

            # replace/update image
            self.img_main.set_image(modified_image, 0, 0, True, cv2.INTER_NEAREST)
        else:
            new_res = (int((end_x - start_x) * self.view_scale), int((end_y - start_y) * self.view_scale))
            portion_cut = cv2.resize(modified_image[start_y:end_y, start_x:end_x, :], new_res, interpolation=cv2.INTER_NEAREST)

            # add grid
            if self.view_scale >= 4.0:
                int_scale = int(self.view_scale)
                density = 2

                old_off = int_scale - 1
                new_off_x = math.ceil((start_x * self.view_scale - old_off) / int_scale) * int_scale + old_off - start_x * self.view_scale
                new_off_y = math.ceil((start_y * self.view_scale - old_off) / int_scale) * int_scale + old_off - start_y * self.view_scale
                portion_cut[new_off_y::int_scale, ::density, :] = 128
                portion_cut[::density, new_off_x::int_scale, :] = 128

            # update region ....
            self.img_main.update_image_region(portion_cut, (int(start_x * self.view_scale), int(start_y * self.view_scale)))

        if resized:
            self.container_images.recalculate_size()

    def update_cc_info(self):
        h, w, _ = self.base_raw.shape

        fake_age = np.zeros((h, w), dtype=np.float32)
        self.base_ccs = Labeler.extractSpatioTemporalContent((255 - self.base_binary), fake_age, False)

        self.base_cc_image = np.zeros((h, w, 3), dtype=np.uint8)

        tempo_sizes = []
        for idx, cc in enumerate(self.base_ccs):
            n_colors = len(GTPixelBinaryAnnotator.CCShowColors)
            cc_color = GTPixelBinaryAnnotator.CCShowColors[idx % n_colors]

            current_cut = self.base_cc_image[cc.min_y:cc.max_y + 1, cc.min_x:cc.max_x + 1]
            cc_mask = cc.img > 0

            current_cut[cc_mask, 0] += cc_color[0]
            current_cut[cc_mask, 1] += cc_color[1]
            current_cut[cc_mask, 2] += cc_color[2]

            tempo_sizes.append(cc.size)

        # self.base_cc_mask = np.sum(self.base_cc_image, 2) == 0
        total_pixels = h * w
        self.stats_pixels_white = self.base_binary.sum() / 255
        self.stats_pixels_black = total_pixels - self.stats_pixels_white
        self.stats_pixels_ratio = self.stats_pixels_black / total_pixels

        cc_sizes = np.array(tempo_sizes)
        self.stats_cc_count = len(self.base_ccs)
        self.stats_cc_size_min = cc_sizes.min()
        self.stats_cc_size_max = cc_sizes.max()
        self.stats_cc_size_mean = cc_sizes.mean()
        self.stats_cc_size_median = np.median(cc_sizes)

        # update interface ...
        self.lbl_pixels_white.set_text("White:\n" + str(self.stats_pixels_white))
        self.lbl_pixels_black.set_text("Black:\n" + str(self.stats_pixels_black))
        self.lbl_pixels_ratio.set_text("Ratio:\n{0:.5f}".format(self.stats_pixels_ratio))

        self.lbl_cc_stats_count.set_text("Total CC: {0}".format(self.stats_cc_count))
        self.lbl_cc_stats_min.set_text("Min:\n{0}".format(self.stats_cc_size_min))
        self.lbl_cc_stats_max.set_text("Max:\n{0}".format(self.stats_cc_size_max))
        self.lbl_cc_stats_mean.set_text("Mean:\n{0:.2f}".format(self.stats_cc_size_mean))
        self.lbl_cc_stats_median.set_text("Median:\n{0:.2f}".format(self.stats_cc_size_median))

    def update_view_scale(self, new_scale):
        prev_scale = self.view_scale

        if (new_scale < 0.25) or (self.max_scale is not None and new_scale > self.max_scale):
            # below minimum or above maximum
            return

        self.view_scale = new_scale

        # keep previous offsets ...
        scroll_offset_y = self.container_images.v_scroll.value if self.container_images.v_scroll.active else 0
        scroll_offset_x = self.container_images.h_scroll.value if self.container_images.h_scroll.active else 0

        prev_center_y = scroll_offset_y + self.container_images.height / 2
        prev_center_x = scroll_offset_x + self.container_images.width / 2

        # compute new scroll bar offsets
        scale_factor = (new_scale / prev_scale)
        new_off_y = prev_center_y * scale_factor - self.container_images.height / 2
        new_off_x = prev_center_x * scale_factor - self.container_images.width / 2

        # update view ....
        try:
            self.update_current_view(True)
        except:
            # rescale failed ...
            # restore previous scale
            self.view_scale = prev_scale
            self.max_scale = prev_scale
            self.update_current_view(True)
            print("Maximum zoom is {0:.2f}%".format(self.max_scale * 100))

        # set offsets
        if self.container_images.v_scroll.active and 0 <= new_off_y <= self.container_images.v_scroll.max:
            self.container_images.v_scroll.value = new_off_y
        if self.container_images.h_scroll.active and 0 <= new_off_x <= self.container_images.h_scroll.max:
            self.container_images.h_scroll.value = new_off_x

        # update scale text ...
        self.lbl_zoom.set_text("Zoom: " + str(int(round(self.view_scale * 100,0))) + "%")

    def btn_zoom_reduce_click(self, button):
        if self.view_scale <= 1.0:
            self.update_view_scale(self.view_scale - 0.25)
        else:
            self.update_view_scale(self.view_scale - 1.0)

    def btn_zoom_increase_click(self, button):
        if self.view_scale < 1.0:
            self.update_view_scale(self.view_scale + 0.25)
        else:
            self.update_view_scale(self.view_scale + 1.0)

    def btn_zoom_zero_click(self, button):
        self.update_view_scale(1.0)

    def btn_view_raw_click(self, button):
        self.view_mode = GTPixelBinaryAnnotator.ViewModeRaw
        self.update_current_view()

    def btn_view_gray_click(self, button):
        self.view_mode = GTPixelBinaryAnnotator.ViewModeGray
        self.update_current_view()

    def btn_view_bin_click(self, button):
        self.view_mode = GTPixelBinaryAnnotator.ViewModeBinary
        self.update_current_view()

    def btn_view_combo_soft_click(self, button):
        self.view_mode = GTPixelBinaryAnnotator.ViewModeSoftCombined
        self.update_current_view()

    def btn_view_combo_hard_click(self, button):
        self.view_mode = GTPixelBinaryAnnotator.ViewModeHardCombined
        self.update_current_view()

    def btn_show_cc_black_click(self, button):
        self.cc_show_mode = GTPixelBinaryAnnotator.CCShowBlack
        self.update_current_view()

    def btn_show_cc_colored_click(self, button):
        self.cc_show_mode = GTPixelBinaryAnnotator.CCShowColored
        self.update_current_view()

    def highlight_scroll_change(self, scroll):
        self.min_highlight_size = int(scroll.value)
        self.lbl_small_highlight.set_text("Highlight CCs smaller than: " + str(self.min_highlight_size))

        self.update_current_view(False)

    def set_editor_mode(self, new_mode):
        self.editor_mode = new_mode

        self.container_confirm_buttons.visible = (new_mode == GTPixelBinaryAnnotator.ModeConfirmCancel)

        self.container_state_buttons.visible = (new_mode == GTPixelBinaryAnnotator.ModeNavigation)
        self.container_stats.visible = (new_mode == GTPixelBinaryAnnotator.ModeNavigation)
        self.btn_edition_start.visible = (new_mode == GTPixelBinaryAnnotator.ModeNavigation)
        self.btn_edition_stop.visible = (new_mode == GTPixelBinaryAnnotator.ModeEdition)

        self.container_edition_mode.visible = (new_mode != GTPixelBinaryAnnotator.ModeGrowCC_Select and
                                               new_mode != GTPixelBinaryAnnotator.ModeGrowCC_Growing and
                                               new_mode != GTPixelBinaryAnnotator.ModeShrinkCC_Select and
                                               new_mode != GTPixelBinaryAnnotator.ModeShrinkCC_Shrinking)

        self.container_view_buttons.visible = (new_mode != GTPixelBinaryAnnotator.ModeGrowCC_Growing and
                                               new_mode != GTPixelBinaryAnnotator.ModeShrinkCC_Shrinking)

        self.container_cc_expansion.visible = (new_mode == GTPixelBinaryAnnotator.ModeGrowCC_Growing or
                                               new_mode == GTPixelBinaryAnnotator.ModeShrinkCC_Shrinking)

        self.btn_edition_expand.visible = (new_mode == GTPixelBinaryAnnotator.ModeNavigation)
        self.btn_edition_shrink.visible = (new_mode == GTPixelBinaryAnnotator.ModeNavigation)

    def btn_edition_start_click(self, button):
        self.pre_edition_binary = self.base_binary.copy()

        self.set_editor_mode(GTPixelBinaryAnnotator.ModeEdition)

    def btn_edition_stop_click(self, button):
        self.undo_stack.append({
            "operation": "pixels_edited",
            "prev_state": self.pre_edition_binary.copy(),
            "new_state": self.base_binary.copy(),
        })

        self.update_cc_info()
        self.update_current_view(False)
        self.set_editor_mode(GTPixelBinaryAnnotator.ModeNavigation)

    def btn_undo_click(self, button):
        if len(self.undo_stack) == 0:
            print("No operations to undo")
            return

        # copy last operation
        to_undo = self.undo_stack[-1]

        success = False

        if to_undo["operation"] == "pixels_edited":
            # restore previous state
            self.base_binary = to_undo["prev_state"].copy()
            success = True

        # removing ...
        if success:
            self.redo_stack.append(to_undo)
            del self.undo_stack[-1]

            # update interface ...
            self.update_cc_info()
            self.update_current_view(False)
        else:
            print("Action could not be undone")

    def btn_redo_click(self, button):
        if len(self.redo_stack) == 0:
            print("No operations to be re-done")
            return

        # copy last operation
        to_redo = self.redo_stack[-1]

        success = False

        if to_redo["operation"] == "pixels_edited":
            # restore new state
            self.base_binary = to_redo["new_state"].copy()
            success = True

        # removing ...
        if success:
            self.undo_stack.append(to_redo)
            del self.redo_stack[-1]

            # update interface ...
            self.update_cc_info()
            self.update_current_view(False)
        else:
            print("Action could not be re-done")

    def btn_return_accept_click(self, button):
        if self.finished_callback is not None:
            # accept ...
            self.finished_callback(True, self.base_binary)

        self.return_screen = self.parent_screen

    def btn_return_cancel_click(self, button):
        if len(self.undo_stack) == 0:
            if self.finished_callback is not None:
                # accept ...
                self.finished_callback(False, None)

            self.return_screen = self.parent_screen
        else:
            self.set_editor_mode(GTPixelBinaryAnnotator.ModeConfirmCancel)

    def img_mouse_down(self, img_object, pos, button):
        # ... first, get click location on original image space
        scaled_x, scaled_y = pos
        click_x = int(scaled_x / self.view_scale)
        click_y = int(scaled_y / self.view_scale)

        if click_x < 0 or click_y < 0 or click_x >= self.base_raw.shape[1] or click_y >= self.base_raw.shape[0]:
            # out of boundaries
            return

        if button == 1 and self.editor_mode == GTPixelBinaryAnnotator.ModeEdition:
            # invert pixel ...
            self.base_binary[click_y, click_x] = 255 - self.base_binary[click_y, click_x]

            if self.base_binary[click_y, click_x] > 0:
                self.base_cc_image[click_y, click_x, :] = 0
            else:
                self.base_cc_image[click_y, click_x, 0] = 1
                self.base_cc_image[click_y, click_x, 1] = 0
                self.base_cc_image[click_y, click_x, 2] = 0

            self.last_motion_set_x = click_x
            self.last_motion_set_y = click_y
            self.last_motion_polarity = self.base_binary[click_y, click_x]

            self.update_current_view(False, (click_x - 1, click_x + 1, click_y - 1, click_y + 1))

        elif button == 1 and self.editor_mode == GTPixelBinaryAnnotator.ModeGrowCC_Select:
            for cc in self.base_ccs:
                if cc.min_x <= click_x <= cc.max_x and cc.min_y <= click_y <= cc.max_y:
                    rel_offset_x = click_x - cc.min_x
                    rel_offset_y = click_y - cc.min_y

                    if cc.img[rel_offset_y, rel_offset_x] > 0:
                        # cv2.imshow("selected", cc.img)
                        # select CC and move to next mode
                        self.pre_edition_binary = self.base_binary.copy()
                        self.selected_CC = cc

                        # compute CC stats ...
                        cc_cut = self.base_gray[cc.min_y:cc.max_y + 1, cc.min_x:cc.max_x + 1, 0].copy()
                        cc_mask = cc.img > 0
                        avg_cc_luminosity = cc_cut[cc_mask].mean()

                        # compute expansion
                        exp_min_x = min(GTPixelBinaryAnnotator.CCExpansionDistance, cc.min_x)
                        exp_max_x = min(GTPixelBinaryAnnotator.CCExpansionDistance, self.base_raw.shape[1] - 1 - cc.max_x)
                        exp_min_y = min(GTPixelBinaryAnnotator.CCExpansionDistance, cc.min_y)
                        exp_max_y = min(GTPixelBinaryAnnotator.CCExpansionDistance, self.base_raw.shape[0] - 1 - cc.max_y)

                        exp_h = cc.getHeight() + exp_min_y + exp_max_y
                        exp_w = cc.getWidth() + exp_min_x + exp_max_x

                        expansion = np.zeros((exp_h, exp_w), np.uint8)
                        expansion[exp_min_y:exp_h - exp_max_y, exp_min_x:exp_w - exp_max_x] = cc.img.copy()

                        dil_kernel = np.ones((1 + exp_min_y + exp_max_y, 1 + exp_min_x + exp_max_x), dtype=np.uint8)
                        dilated = cv2.dilate(expansion, np.array(dil_kernel, dtype=np.uint8))
                        expansion = dilated - expansion

                        abs_exp_min_x = cc.min_x - exp_min_x
                        abs_exp_max_x = cc.max_x + exp_max_x + 1
                        abs_exp_min_y = cc.min_y - exp_min_y
                        abs_exp_max_y = cc.max_y + exp_max_y + 1

                        expanded_cut = self.base_gray[abs_exp_min_y:abs_exp_max_y, abs_exp_min_x:abs_exp_max_x, 0].copy()
                        expanded_cut = np.abs(avg_cc_luminosity - expanded_cut.astype(np.float64))

                        expansion_pixels = []
                        for y in range(expanded_cut.shape[0]):
                            for x in range(expanded_cut.shape[1]):
                                if expansion[y, x] > 0:
                                    expansion_pixels.append((expanded_cut[y, x], abs_exp_min_y + y, abs_exp_min_x + x))

                        self.CC_expansion_pixels = sorted(expansion_pixels)
                        self.count_expand = 0

                        self.expansion_scroll.reset(0, len(self.CC_expansion_pixels), 0, 1)

                        self.set_editor_mode(GTPixelBinaryAnnotator.ModeGrowCC_Growing)
                        # force one view
                        self.view_mode = GTPixelBinaryAnnotator.ViewModeSoftCombined
                        self.cc_show_mode = GTPixelBinaryAnnotator.CCShowBlack
                        self.update_current_view()
                        break

        elif button == 1 and self.editor_mode == GTPixelBinaryAnnotator.ModeShrinkCC_Select:
             for cc in self.base_ccs:
                if cc.min_x <= click_x <= cc.max_x and cc.min_y <= click_y <= cc.max_y:
                    rel_offset_x = click_x - cc.min_x
                    rel_offset_y = click_y - cc.min_y

                    if cc.img[rel_offset_y, rel_offset_x] > 0:
                        self.selected_CC = cc
                        self.pre_edition_binary = self.base_binary.copy()

                        # compute CC stats ...
                        cc_cut = self.base_gray[cc.min_y:cc.max_y + 1, cc.min_x:cc.max_x + 1, 0].copy()
                        cc_mask = cc.img > 0
                        avg_cc_luminosity = cc_cut[cc_mask].mean()

                        erode_kernel = np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]], dtype=np.uint8)
                        exp_img = np.zeros((cc.img.shape[0] + 2, cc.img.shape[1] + 2),  dtype=np.uint8)
                        exp_img[1:1 + cc.img.shape[0], 1:1 + cc.img.shape[1]] = cc.img.copy()
                        shrinked = cv2.erode(exp_img, erode_kernel)
                        reduction = exp_img - shrinked
                        reduction = reduction[1:1 + cc.img.shape[0], 1:1 + cc.img.shape[1]]

                        reduction_pixels = []
                        for y in range(cc_cut.shape[0]):
                            for x in range(cc_cut.shape[1]):
                                if reduction[y, x] > 0:
                                    reduction_pixels.append((abs(avg_cc_luminosity - cc_cut[y, x]), cc.min_y + y, cc.min_x + x))

                        self.CC_shrinking_pixels = sorted(reduction_pixels, reverse=True)
                        self.count_shrink = 0

                        self.expansion_scroll.reset(0, len(self.CC_shrinking_pixels), 0, 1)
                        self.set_editor_mode(GTPixelBinaryAnnotator.ModeShrinkCC_Shrinking)

                        # force one view
                        self.view_mode = GTPixelBinaryAnnotator.ViewModeSoftCombined
                        self.cc_show_mode = GTPixelBinaryAnnotator.CCShowBlack
                        self.update_current_view()
                        break

    def img_mouse_motion(self, img_object, pos, rel, buttons):
        if buttons[0] > 0 and self.editor_mode == GTPixelBinaryAnnotator.ModeEdition:

            scaled_x, scaled_y = pos
            move_x = int(scaled_x / self.view_scale)
            move_y = int(scaled_y / self.view_scale)

            if move_x < 0 or move_y < 0 or move_x >= self.base_raw.shape[1] or move_y >= self.base_raw.shape[0]:
                # out of boundaries
                return

            if (move_x != self.last_motion_set_x or move_y != self.last_motion_set_y) and (self.base_binary[move_y, move_x] != self.last_motion_polarity):
                # invert pixel ...
                self.base_binary[move_y, move_x] = 255 - self.base_binary[move_y, move_x]
                if self.base_binary[move_y, move_x] > 0:
                    self.base_cc_image[move_y, move_x, :] = 0
                else:
                    self.base_cc_image[move_y, move_x, 0] = 1
                    self.base_cc_image[move_y, move_x, 1] = 0
                    self.base_cc_image[move_y, move_x, 2] = 0

                self.last_motion_set_x = move_x
                self.last_motion_set_y = move_y

                self.update_current_view(False, (move_x - 1, move_x + 1, move_y - 1, move_y + 1))
        else:
            self.last_motion_set_x = None
            self.last_motion_set_y = None
            self.last_motion_polarity = None

    def btn_confirm_accept_click(self, button):
        if self.editor_mode == GTPixelBinaryAnnotator.ModeConfirmCancel:
            if self.finished_callback is not None:
                # accept ...
                self.finished_callback(False, None)

            self.return_screen = self.parent_screen

    def btn_confirm_cancel_click(self, button):
        # simply return to navigation mode mode
        self.set_editor_mode(GTPixelBinaryAnnotator.ModeNavigation)

    def btn_edition_expand_click(self, button):
        self.set_editor_mode(GTPixelBinaryAnnotator.ModeGrowCC_Select)

    def btn_edition_shrink_click(self, button):
        self.set_editor_mode(GTPixelBinaryAnnotator.ModeShrinkCC_Select)

    def btn_expand_accept_click(self, button):
        if self.editor_mode == GTPixelBinaryAnnotator.ModeGrowCC_Growing:
            # apply changes
            for value, y, x in self.CC_expansion_pixels[:self.count_expand]:
                # mark as content ...
                self.base_binary[y, x] = 0

            self.CC_expansion_pixels = None
        elif self.editor_mode == GTPixelBinaryAnnotator.ModeShrinkCC_Shrinking:
            # apply changes
            for value, y, x in self.CC_shrinking_pixels[:self.count_shrink]:
                # mark as background ...
                self.base_binary[y, x] = 255

            self.CC_shrinking_pixels = None

        # clear selection
        self.selected_CC = None

        # finish edition
        self.btn_edition_stop_click(self.btn_edition_stop)
        self.update_current_view()

    def btn_expand_cancel_click(self, button):
        # clear selection
        self.selected_CC = None
        self.CC_expansion_pixels = None

        # go back to navigation mode
        self.set_editor_mode(GTPixelBinaryAnnotator.ModeNavigation)
        self.update_current_view()

    def expansion_scroll_change(self, scroll):
        if self.editor_mode == GTPixelBinaryAnnotator.ModeGrowCC_Growing:
            self.count_expand = scroll.value
            self.lbl_cc_expansion.set_text("Expand CC, Pixels = " + str(self.count_expand))
        elif self.editor_mode == GTPixelBinaryAnnotator.ModeShrinkCC_Shrinking:
            self.count_shrink = scroll.value
            self.lbl_cc_expansion.set_text("Shrinking CC, Pixels = " + str(self.count_shrink))

        start_x = self.selected_CC.min_x - GTPixelBinaryAnnotator.CCExpansionDistance
        end_x = self.selected_CC.max_x + GTPixelBinaryAnnotator.CCExpansionDistance
        start_y = self.selected_CC.min_y - GTPixelBinaryAnnotator.CCExpansionDistance
        end_y = self.selected_CC.max_y + GTPixelBinaryAnnotator.CCExpansionDistance

        self.update_current_view(False, (start_x, end_x, start_y, end_y))


# 有一堆水龙头（浇水范围不同），需要几个水龙头，才能把 0...n 都浇到水
# 如果没全部照顾到 就要 return -1
class Solution:
    def minTaps(self, n: int, ranges: List[int]) -> int:
        table = [inf]*(n+1)
        # table[i] 到0...i范围全部照顾到，“共需几个水龙头”
        table[0] = 0 # 0...0 长度为0的区域，需要0个tap
        for i in range(n+1): # 因题目的 len(ranges) 是 n+1
            r = ranges[i] # 这次 tap i 可照顾到的距离，照顾 i-r..i+r
            left, right = max(0, i-r), min(n, i+r)
            print(i, left, right)
            for k in range(left, right+1): # 逐一巡tap i 可浇的范围
            # 想法：0...k只要开table[k]个tap, 再配上 tap i 便能照顾到最right
                if table[k]+1 < table[right]:
                    table[right] = table[k] + 1 # 所以 table[k] + 1
            # print(table)
        if table[n] == inf: # 若 0...n 全部照顾到，竟然需要 inf个，那没救
            return -1
        return table[n] # 这个，能照顾到 0...n