from os import error
import numpy as np
import pytest

from sdia_python.lab2.box_window import BoxWindow, UnitBoxWindow

def test_raise_assertion_error_when_points_is_not_an_array():
    with pytest.raises(AssertionError):
        # call_something_that_raises_TypeError()
        L = [[1, 2], [3, 4]]
        box = BoxWindow(L)
        raise AssertionError()

def test_raise_Exception_when_bounds_are_incorrect():
    with pytest.raises(Exception):
        L = np.array([[2, 1], [3, 4]])
        box = BoxWindow(L)
        raise Exception

def test_raise_Exception_when_dimension_is_incorrect():
    with pytest.raises(Exception):
        L = np.array([[1, 2, 4], [3, 4, 5]])
        box = BoxWindow(L)
        raise Exception

@pytest.mark.parametrize(
    "bounds, expected",
    [
        (np.array([[2.5, 2.5]]), "BoxWindow: [2.5, 2.5]"),
        (np.array([[0, 5], [0, 5]]), "BoxWindow: [0, 5] x [0, 5]"),
        (
            np.array([[0, 5], [-1.45, 3.14], [-10, 10]]),
            "BoxWindow: [0.0, 5.0] x [-1.45, 3.14] x [-10.0, 10.0]",
        ),
    ],
)
def test_box_string_resentation(bounds, expected):
    assert str(BoxWindow(bounds)) == expected

@pytest.fixture
def box_2d_05():
    return BoxWindow(np.array([[0, 5], [0, 5]]))

@pytest.mark.parametrize(
    "point, expected",
    [
        (np.array([0, 0]), True),
        (np.array([2.5, 2.5]), True),
        (np.array([-1, 5]), False),
        (np.array([10, 3]), False),
    ],
)
def test_indicator_function_box_2d(box_2d_05, point, expected):
    is_in = box_2d_05.indicator_function(point)
    assert is_in == expected

@pytest.mark.parametrize(
    "point, expected",
    [
        (np.array([0, 0]), True),
        (np.array([2.5, 2.5]), True),
        (np.array([-1, 5]), False),
        (np.array([10, 3]), False),
    ],
)
def test_contains_function_box_2d(box_2d_05, point, expected):
    is_in = box_2d_05.__contains__(point)
    assert is_in == expected

# ================================
# ==== WRITE YOUR TESTS BELOW ====
# ================================

def test_raise_error_when_dimension_didnot_match_with_point():
    with pytest.raises(AssertionError):
        L = np.array([[1, 2], [3, 4]])
        box = BoxWindow(L)
        box.__contains__(np.array([0.5, 3.5, 2.5]))
        raise AssertionError

@pytest.mark.parametrize(
    "box, expected",
    [
        (np.array([[1, 2]]), 1),
        (np.array([[1, 2], [3, 4]]), 2),
        (np.array([[1, 2], [3, 4], [5, 6]]), 3),
        (np.array([[1, 2], [3, 4], [5, 6], [1, 2], [3, 4], [5, 6]]), 6),
    ],
)
def test_len_box(box, expected):
    assert len(BoxWindow(box)) == expected

@pytest.mark.parametrize(
    "box, expected",
    [
        (np.array([[1, 2]]), 1),
        (np.array([[1, 2], [3, 4]]), 2),
        (np.array([[1, 2], [3, 4], [5, 6]]), 3),
        (np.array([[1, 2], [3, 4], [5, 6], [1, 2], [3, 4], [5, 6]]), 6),
    ],
)
def test_dimension_box(box, expected):
    assert BoxWindow(box).dimension() == expected

@pytest.mark.parametrize(
    "box, expected",
    [
        (np.array([[1, 2]]), 1),
        (np.array([[1, 3], [3, 5]]), 4),
        (np.array([[1, 2], [3, 4], [5, 7]]), 2),
        (np.array([[1, 2], [3, 5], [5, 9], [1, 2], [3, 5], [5, 6]]), 16),
    ],
)
def test_volume_box(box, expected):
    assert BoxWindow(box).volume() == expected

@pytest.mark.parametrize(
    "box, expected",
    [
        (np.array([[1, 2]]), np.array([1.5])),
        (np.array([[1, 3], [3, 5]]), np.array([2, 4])),
        (np.array([[1, 2], [3, 4], [5, 7]]), np.array([1.5, 3.5, 6.0])),
        (
            np.array([[1, 2], [3, 5], [5, 9], [1, 2], [3, 5], [5, 6]]),
            np.array([1.5, 4.0, 7.0, 1.5, 4.0, 5.5]),
        ),
    ],
)
def test_center_box(box, expected):
    assert np.array_equal(BoxWindow(box).center(), expected)

@pytest.mark.parametrize(
    "bounds",
    [
        (np.array([[1, 2]])),
        (np.array([[1, 3], [3, 5]])),
        (np.array([[1, 2], [3, 4], [5, 7]])),
        (np.array([[1, 2], [3, 5], [5, 9], [1, 2], [3, 5], [5, 6]])),
    ],
)
def test_rand_onepoint(bounds):
    box = BoxWindow(bounds)
    assert box.__contains__(box.rand()[0])

def test_rand_multiplepoint_3dimension():
    box = BoxWindow(np.array([[1, 2], [10, 15.5], [3.5, 7]]))
    coord = box.rand(100)
    for value in coord:
        assert box.__contains__(value)

def test_raise_error_when_center_is_not_an_array():
    with pytest.raises(AssertionError):
        center = [1, 2, 3]
        box = UnitBoxWindow(center)
        raise AssertionError

@pytest.mark.parametrize(
    "center, expected",
    [
        (np.array([0]), "BoxWindow: [-0.5, 0.5]"),
        (np.array([0, 0]), "BoxWindow: [-0.5, 0.5] x [-0.5, 0.5]"),
        (np.array([0, 0, 0]), "BoxWindow: [-0.5, 0.5] x [-0.5, 0.5] x [-0.5, 0.5]",),
    ],
)
def test_UnitBoxWindow(center, expected):
    unitBox = UnitBoxWindow(center)
    assert unitBox.__str__() == expected

@pytest.mark.parametrize(
    "center, expected",
    [
        (np.array([2.5]), "BoxWindow: [2.0, 3.0]"),
        (np.array([1.5, 4]), "BoxWindow: [1.0, 2.0] x [3.5, 4.5]"),
        (np.array([2.5, 8, -4.5]), "BoxWindow: [2.0, 3.0] x [7.5, 8.5] x [-5.0, -4.0]"),
    ],
)
def test_UnitBoxWindow_with_center_specified(center, expected):
    unitBox = UnitBoxWindow(center)
    assert unitBox.__str__() == expected

@pytest.mark.parametrize(
    "center", [(np.array([2.5])), (np.array([1.5, 4])), (np.array([2.5, 8, -4.5])),],
)
def test_UnitBoxWindow_volume_is_equal_to_one(center):
    unitBox = UnitBoxWindow(center)
    assert unitBox.volume() == 1


import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.ticker import FuncFormatter, MultipleLocator, FormatStrFormatter  # 坐标轴设置

"""
Dateframe和Array的相互转换
    np_array = df.values
    df = pd.DataFrame(np_array, index=[], columns=[])

matplotlib.pyplot
    https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.html

显示中文/特殊符号和转义字符
    1. u'\u2103'是摄氏度符号，前面的u代表unicode，引号中是该符号对应的unicode编码
    2. 显示中文: plt.xlabel((u'中文标题', fontproperties='SimHei')
    3. 显示公式: plt.xlabel('Rice('+r'$\mu\mathrm{mol}$'+' '+'$ \mathrm{m}^{-2} \mathrm{s}^{-1}$'+')')
    4. 文本中的空格需要转义符 'hello\ world'

字体的设置：
    https://blog.csdn.net/helunqu2017/article/details/78659490
    fontsize=12,  ['xx-small', 'x-small', 'small', 'medium', 'large','x-large', 'xx-large']
    fontweight='bold',  ['light', 'normal', 'medium', 'semibold', 'bold', 'heavy', 'black']
    fontstyle设置字体类型，可选参数[ 'normal' | 'italic' | 'oblique' ]，italic斜体，oblique倾斜
    verticalalignment设置水平对齐方式 ，可选参数 ： 'center' , 'top' , 'bottom' ,'baseline'
    horizontalalignment设置垂直对齐方式，可选参数：left,right,center
    rotation(旋转角度)可选参数为:vertical,horizontal 也可以为数字
    alpha透明度，参数值0至1之间
    backgroundcolor标题背景颜色
    bbox给标题增加外框 ，常用参数如下：
        boxstyle方框外形
        facecolor(简写fc)背景颜色
        edgecolor(简写ec)边框线条颜色
        edgewidth边框线条大小

线条和点的样式设置:
    linestyle='--' 或 linefmt='--'
    参考1: https://matplotlib.org/stable/gallery/lines_bars_and_markers/linestyles.html
    参考2: https://matplotlib.org/2.1.1/api/_as_gen/matplotlib.pyplot.plot.html

    color='red'
    参考1: https://matplotlib.org/stable/tutorials/colors/colors.html
    参考2: 颜色代码表 https://www.jianshu.com/p/f674e71b429c
    参考3: 颜色色系 https://zhuanlan.zhihu.com/p/65220518

    marker='|' 或 markerfmt='|'
    参考1: https://matplotlib.org/stable/api/markers_api.html

    综合参考: https://www.cnblogs.com/darkknightzh/p/6117528.html
"""

def foo_plt_save_graphs(title_, path_=None):
    if path_ is not None:
        plt.savefig(os.path.join(path_, title_+'.png'), dpi=120)
    else:
        if not os.path.exists('output_graphs'):
            os.mkdirs('output_graphs')
            plt.savefig(os.path.join('output_graphs', title_+'.png'), dpi=120)

def foo_plt_basic_setting(title_=None, xlabel_=None, ylabel_=None,
                xlim_=None, ylim_=None,
                is_legend=True, is_grid=True):
    if title_ is not None:
        plt.title(title_)
        # plt.title(_title, loc='left', verticalalignment='bottom', fontsize=12, fontweight='bold', color='blue',
        #           rotation=3, bbox=dict(facecolor='g', edgecolor='blue', alpha=0.5))
    if xlabel_ is not None:
        plt.xlabel(xlabel_)
        # plt.xlabel(_xlabel, fontsize=8, fontproperties='SimHei')
    if ylabel_ is not None:
        plt.ylabel(ylabel_)
    if xlim_ is not None:
        plt.xlim(xlim_)  # 元组(min, max)
    if ylim_ is not None:
        plt.ylim(ylim_)  # 元组(min, max)
    if is_legend is not None:
        plt.legend(prop={'size': 8})
        # plt.legend(loc=0, prop={'size': 10, 'weight': 'normal', 'family': 'Times New Roman'})
        # https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.legend.html
    if is_grid:
        # plt.grid()
        plt.grid(which='major', axis='both', linestyle='-.')  # ['major','minor','both'] ['x','y','both']
        # https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.grid.html

def foo_plt_advanced_setting():
    # 添加文本注释, xy是注释点坐标, xytext是文本坐标, 可以箭头(文本指向注释点)
    plt.annotate('Annotate', xy=(8, 5), xytext=(5, 40), arrowprops=dict(facecolor='red', shrink=0))

    # 设置画布背景色
    plt.gcf().set_facecolor('#CCFFFF')

    # # 设置轴值范围[xmin,xmax,ymin,ymax], 等效于同时设置plt.xlim + ylim
    # plt.axis([0, 10, 0, 100])
    #
    # # 设置轴标签, 第一个数组参数是值，第二个数组参数文本显示(可选)
    # plt.xticks([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
    # plt.yticks([0, 20, 60, 80, 100],
    #            [r'really\ bad', r'$bad$', r'$normal$', r'$good$', r'$readly\ good$'])

def foo_axes_advanced_setting():
    """ 图表区的详细设置, 图表(axes)的设置会覆盖绘图板(plt)的设置 """
    ax = plt.gca()  # Get Current Axes'

    # 设置图表区域边框颜色(无色为'none')
    ax.spines['top'].set_color('red')
    ax.spines['bottom'].set_color('blue')
    ax.spines['left'].set_color('green')
    ax.spines['right'].set_color('yellow')

    # 设置图表区域边框位置
    ax.spines['bottom'].set_position(('data', 10))
    ax.spines['left'].set_position(('data', 2))

    # # 获取坐标轴标签的信息: 标签/对应值/格式
    # ax.xaxis.get_major_ticks()
    # ax.xaxis.get_minor_ticks()
    # ax.xaxis.get_major_locator()
    # ax.xaxis.get_minor_locator()
    # ax.xaxis.get_major_formatter()
    # ax.xaxis.get_minor_formatter()

    # 设置坐标轴标签位置
    ax.xaxis.set_ticks_position('top')
    ax.yaxis.set_ticks_position('right')

    # 设置坐标轴标签和样式
    ax.set_xticks([1, 2, 3, 4, 5])
    ax.set_xticklabels(['a', 'b', 'c', 'd', 'e'])
    ax.set_xlim(0, 11)
    ax.set_xlabel('X axis', {'family': 'Times New Roman', 'weight': 'normal', 'size': 20, })

    # 设置坐标轴线样式
    ax.axhline(0, color="k", clip_on=False)

    # 设置图例
    ax.legend()

    # 设置主/次轴标签(设置后才有主/次网格线)
    ax.xaxis.set_major_locator(MultipleLocator(2))  # plt.MultipleLocator相同
    ax.xaxis.set_minor_locator(MultipleLocator(0.5))
    ax.yaxis.set_major_locator(MultipleLocator(10))
    ax.yaxis.set_minor_locator(MultipleLocator(5))
    ax.grid(which='minor', axis='x', color='orangered', linestyle=':', linewidth=0.75)

    ax.xaxis.grid(True, which='major')  # x坐标轴的网格使用主刻度
    ax.yaxis.grid(True, which='minor')  # y坐标轴的网格使用次刻度

    # 设置刻度标签的文本格式
    ax.xaxis.set_major_formatter(FormatStrFormatter('%.0f'))

    # 使用用户函数来定义刻度格式
    # 首先要定义一个格式函数, 需要两个参数:标签内容值和位置, 返回对应位置的格式化后的字符串
    def _foo_tick(x, pos):
        # x:  tick value - ie. what you currently see in yticks
        # pos: a position - ie. the index of the tick (from 0 to 9 in this example)
        if not x % 1.0:
            return ''
        return '%.2f' % x
    # FuncFormatter将格式函数转换为formatter对象
    ax.xaxis.set_minor_formatter(FuncFormatter(_foo_tick))

    # 设置坐标轴标签格式
    ax.tick_params('x', which='minor', length=5, width=1.0, labelsize=5, labelcolor='0.25')

    # # 删除坐标轴的标签刻度
    # ax.yaxis.set_major_locator(plt.NullLocator())
    # ax.xaxis.set_major_formatter(plt.NullFormatter())

def foo_plt_plot(label_, *args):
    """
    https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html#matplotlib.pyplot.plot
    :param label_: 数据标签
    :param args: x值, y值
    :return: none
    """
    # if len(args) == 1:
    #     plt.plot(*zip(*enumerate(args[0])), label='%s [Mean:%.2f]' % (label_, np.mean(args[0])))
    # elif len(args) > 1:
    #     plt.plot(args[0], args[1], label='%s [Mean:%.2f]' % (label_, np.mean(args[1])))

    # 更多参数样例:
    plt.plot(args[0], args[1], marker='o', markersize=2, linewidth=2)
    # plot可以用数组绘制并返回线条组: linesList=plt.plot(x1, y1, x2, y2, x3, y3..)
    # 用plt.setp方法可以同时设置多个线条的属性, plt.setp(linesList, color='r')

def foo_plt_scatter(label_, arr_x, arr_y):
    """
    https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.scatter.html#matplotlib.pyplot.scatter
    :param label_: 数据标签
    :param arr_x: x值
    :param arr_y: y值
    :return: None
    """
    plt.scatter(arr_x, arr_y, label='%s [Mean:%.2f]' % (label_, np.mean(arr_y)))
    # 更多参数样例:
    # plt.scatter(arr_x, arr_y, s=10, color='blue', marker='*', alpha=0.5, linewidths=2, edgecolors='red')

    # # 扩展: 定义一个长度和数据一致的颜色数组为每一个点染色
    # _colours = ['Crimson', 'Blue', 'Fuchsia', 'Tomato', 'Indigo', 'Turquoise', 'Brown', 'Wheat']
    # plt.scatter(_arr_x, _arr_x, color=_colours)

def foo_plt_stem(label_, arr_x, arr_y):
    """
    https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.stem.html
    :param label_: 数据标签
    :param arr_x: x值
    :param arr_y: y值
    :return: None
    """
    _markerline, _stemlines, _baseline = plt.stem(arr_x, arr_y, label=label_)
    # 更多参数样例:
    # plt.stem(arr_x, arr_y, label=_label, linefmt='--', markerfmt='d', basefmt='C13-', bottom=10)

    # # 单独或批量设置棉棒末端, 棉棒连线和基线的属性
    # plt.setp(_markerline, color='k')  # 将棉棒末端设置为黑色

def foo_plt_bar(label_, arr_bars, arr_tag=None, bottom_=0, width_=0.75, overlap=False):
    """
    https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.bar.html
    pyplot的柱状图是绘制相对基准值(bottom, 默认为0)的偏离度(height)
    本函数为绝对柱形图,大于基准值柱形向上,小于基准值柱形向下
    :param label_: 数据标签
    :param arr_bars: 嵌套列表,第一层为数据组,第二层为数据值系列
    :param arr_tag: x轴标签
    :param bottom_: 基准值
    :param width_: 柱形总宽度(多组数据均分)
    :return: None
    """
    def tick_shift(x, pos, bottom):
        return '%.0f' % (x + bottom)

    _num = len(arr_bars)
    if overlap:
        _width, _shift = width_, width_/_num
    else:
        _width, _shift = width_/_num, width_/_num

    for _i in range(_num):
        _bars = np.asarray(arr_bars[_i]) - bottom_
        arr_x = np.asarray(range(len(_bars))) + _i * _shift
        plt.bar(arr_x, _bars, label=label_, width=_width)
        # 更多参数样例:
        # plt.bar(arr_x, arr_y, alpha=0.9, width=0.35, facecolor='lightskyblue', edgecolor='white', lw=1)

    formatter = FuncFormatter(lambda x, pos: tick_shift(x, pos, bottom_))
    ax = plt.gca()
    ax.yaxis.set_major_formatter(formatter)

    if arr_tag is not None:
        plt.xticks(range(len(arr_tag)), arr_tag)

def gen_sample_data_array(n=100):
    from random import randint
    _arr_x = np.linspace(2, 10, n)
    _arr_y = _arr_x ** 4 * randint(1, 5) * randint(-1, 1) + \
             _arr_x ** 3 * randint(1, 8) * randint(-1, 1) + \
             _arr_x ** 2 * randint(1, 10) * randint(-1, 1) + \
             _arr_x * randint(1, 15) * randint(-1, 1)
    _arr_d = np.random.randint(-500, 500, n)
    return _arr_x, _arr_y + _arr_d

if __name__ == "__main__":
    """ matplotlib.pyplot绘图 """
    # 新建绘图画板, 默认会生成一个axes(画布/绘图区)，一个画板上可以有多个画布
    # plt.figure()

    # for i in range(3):
    #     x_values, y_values = gen_sample_data_array()
    #     # 添加散点平滑线
    #     foo_plt_plot('sample-%d' % i, x_values, y_values)
    #
    # x_values, y_values = gen_sample_data_array()
    # # 添加散点图
    # foo_plt_scatter('sample-scatter', x_values, y_values)
    #
    # x_values, y_values = gen_sample_data_array(10)
    # # 添加棉棒图
    # foo_plt_stem('sample-stem', x_values, y_values)
    #
    # arr_bars = []
    # for i in range(2):
    #     x_values, y_values = gen_sample_data_array(10)
    #     arr_bars.append(y_values)
    # # # 添加柱形图图
    # foo_plt_bar('sample-bar', arr_bars, bottom_=-2, arr_tag=['a','b','c','c','e','f','g'], width_=0.7)

    # # 设置图表基础格式,如标题/轴标题/网格/轴值域等
    # foo_plt_basic_setting(title_='This_is_a_sample', xlabel_='x value(dB)', ylabel_='y value(Mbit/s)',
    #                       xlim_=None, ylim_=None)
    #
    # foo_plt_advanced_setting()
    # foo_axes_advanced_setting()

    """ seanborn绘图 """
    arr = []
    for i in range(3):
        x_values, y_values = gen_sample_data_array(30)
        arr.append(np.transpose(np.vstack((x_values, y_values, np.ones_like(x_values) * i, np.ones_like(x_values) * i,
                                           np.random.randint(1, 5, size=30)))))

    # 使用dataframe格式作为seaborn数据源更便捷
    df = pd.DataFrame(np.concatenate(arr), columns=['x', 'y', 'hue', 'style', 'size'])

    ''' seanborn散点图scatterplot/线图lineplot/关系图relplot: 一般散点图 '''
    # # relplot是scatterplot和lineplot的集合, 通过参数指定类型kind='line'
    # # 常用参数:
    # # hue, style, size分组
    # # 颜色palette=['b', 'r']
    # # 分别画子图col='time',row='sex'
    # ax = sns.scatterplot(x='x', y='y', hue='hue', data=df)
    # # ax = sns.lineplot(x='x', y='y', markers=True, dashes=False, data=df)

    ''' seanborn直方图distplot: 变量的分布规律 '''
    # # 常用参数:
    # # kde=False, 不显示密度曲线
    # # ax=axes[0], 指定画布
    # # rug=True, 显示边际毛毯
    # # hist_kws, kde_kws, rug_kws, 详细的格式设置
    #
    # # 创建1行2列画布方便对比
    # fig, axes = plt.subplots(1, 2)
    #
    # hue = df['hue'].dropna()  # distplot不能处理缺失数据, 需要去除缺失值(可选)
    # sns.distplot(hue, rug=True, ax=axes[0])
    # sns.distplot(df['size'], kde=False, ax=axes[1], hist_kws={'color': 'green', 'label': 'hist'})

    ''' seanborn条形图barplot: 数值变量的集中趋势和置信区间 '''
    # # 常用参数:
    # # estimator=median/max, 统计方法
    # # order/hue_order, 标题数组, 可以控制条形图的顺序
    # # orient=v/h 绘图方向
    # # errcolor, errwidth 误差线的格式
    #
    # sns.barplot(x='style', y='y', hue='size', data=df)
    # # ax = plt.gca()
    # # ax.axhline(0, color="k", clip_on=False)
    #
    # # countplot即简化版的barplot, 只有计数的柱形
    # # sns.countplot(x='style', hue='size', data=df)
    # # sns.countplot(y='style', hue='size', data=df)

    ''' seanborn分布密度散点图 stripplot/swarmplot: 观察数据分布 '''
    # # stripplot是随机抖动展开, swarmplot则是不重复展开,适用于小数据量
    # # 常用参数:
    # # jitter=1, 调整抖动范围
    # fig, axes = plt.subplots(1, 2)
    # sns.stripplot(x='size', y='y', hue='style', data=df, ax=axes[0])
    # sns.swarmplot(x='size', y='y', hue='style', data=df, ax=axes[1])

    ''' seanborn箱线图 stripplot: 数据分散情况统计[异常值/极大极小值/中位数/上下四分位数] '''
    # # 可以叠加分布散点图:
    # sns.boxplot(x='style', y='size', data=df, linewidth=1)

    ''' seanborn小提琴图 violinplot: 箱线图和密度散点的结合 '''
    # # 常用参数:
    # # 当hue分组为2时，可以通过设置split参数左右显示
    # # inner='stick'/None, 内部图形
    # # 可以叠加分布散点图
    # sns.violinplot(x='style', y='size', data=df, linewidth=1, inner='stick')

    ''' seanborn 线性回归图replot/ 回归模型图lmplot: 即散点图+趋势线, 分析变量关联关系 '''
    # # 常用参数:
    # # 颜色分组hue, 样式分组style, 大小分组size
    # # 用col代替hue, 会分开作图
    # # color, marker可以是数组对应多个分组
    # # ci=None, 置信区间(拟合线的阴影)
    # # order 多项式拟合阶数
    # # lowess=True 局部加权回归散点平滑法(locally weighted scatterplot smoothing，LOWESS)，是一种非参数回归拟合的方式，
    # # 其主要思想是选取一定比例的局部数据，拟合多项式回归曲线，以便观察到数据的局部规律和趋势。适用非线性关系。

    # sns.regplot(x='x', y='y', data=df, hue='hue', style='style', size='size')
    sns.lmplot(x='x', y='y', data=df, hue='hue', lowess=True)
    # # sns.lmplot(x='x', y='y', data=df)

    ''' seanborn复合图表联合分布图jointplot, 中央图+维度分布'''
    # # 常用参数:
    # # kind : { “scatter” | “reg” | “resid” | “kde” | “hex” }。默认散点图；
    # # stat_func：用于计算统计量关系的函数；
    # # ratio：中心图与侧边图的比例，越大、中心图占比越大；
    # # dropna：去除缺失值；
    # # height：图的尺度大小（正方形）；
    # # space：中心图与侧边图的间隔大小；
    # # xlim，ylim：x，y的范围
    # sns.jointplot(x='x', y='y', kind='kde', data=df)

    ''' seanborn热力图heatmap '''
    # ax = sns.heatmap(data=df[['x', 'y']], annot=True, annot_kws={'size': 9, 'weight': 'bold', 'color': 'w'}, fmt='.2f')

    # seanborn定义主题风格 [ticks, dark, white, darkgrid, whitegrid]
    sns.set(style='darkgrid')
    sns.set_style('darkgrid')
    # axes_styles

    # 字体大小sns.set_context [paper, notebook, talk, poster]
    sns.plotting_context("notebook")

    plt.show()


#!/usr/bin/env python
# coding: utf-8

# # Self-Driving Car Engineer Nanodegree
#
#
# ## Project: **Finding Lane Lines on the Road**
# ***
# In this project, you will use the tools you learned about in the lesson to identify lane lines on the road.  You can develop your pipeline on a series of individual images, and later apply the result to a video stream (really just a series of images). Check out the video clip "raw-lines-example.mp4" (also contained in this repository) to see what the output should look like after using the helper functions below.
#
# Once you have a result that looks roughly like "raw-lines-example.mp4", you'll need to get creative and try to average and/or extrapolate the line segments you've detected to map out the full extent of the lane lines.  You can see an example of the result you're going for in the video "P1_example.mp4".  Ultimately, you would like to draw just one line for the left side of the lane, and one for the right.
#
# In addition to implementing code, there is a brief writeup to complete. The writeup should be completed in a separate file, which can be either a markdown file or a pdf document. There is a [write up template](https://github.com/udacity/CarND-LaneLines-P1/blob/master/writeup_template.md) that can be used to guide the writing process. Completing both the code in the Ipython notebook and the writeup template will cover all of the [rubric points](https://review.udacity.com/#!/rubrics/322/view) for this project.
#
# ---
# Let's have a look at our first image called 'test_images/solidWhiteRight.jpg'.  Run the 2 cells below (hit Shift-Enter or the "play" button above) to display the image.
#
# **Note: If, at any point, you encounter frozen display windows or other confounding issues, you can always start again with a clean slate by going to the "Kernel" menu above and selecting "Restart & Clear Output".**
#
# ---

# **The tools you have are color selection, region of interest selection, grayscaling, Gaussian smoothing, Canny Edge Detection and Hough Tranform line detection.  You  are also free to explore and try other techniques that were not presented in the lesson.  Your goal is piece together a pipeline to detect the line segments in the image, then average/extrapolate them and draw them onto the image for display (as below).  Once you have a working pipeline, try it out on the video stream below.**
#
# ---
#
#

#
#

#

#
 Your output should look something like this (above) after detecting line segments using the helper functions below

#

#

#

#

#
#

#

#
 Your goal is to connect/average/extrapolate line segments to get output like this

#

#

# **Run the cell below to import some packages.  If you get an `import error` for a package you've already installed, try changing your kernel (select the Kernel menu above --> Change Kernel).  Still have problems?  Try relaunching Jupyter Notebook from the terminal prompt.  Also, consult the forums for more troubleshooting tips.**

# ## Import Packages

# In[ ]:

#importing some useful packages
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
import cv2
import logging
from matplotlib.widgets import Slider
#get_ipython().run_line_magic('matplotlib', 'inline')

# ## Read in an Image

# In[ ]:

#reading in an image
image = mpimg.imread('test_images/solidWhiteRight.jpg')

#printing out some stats and plotting
print('This image is:', type(image), 'with dimensions:', image.shape)
# plt.imshow(image)  # if you wanted to show a single color channel image called 'gray', for example, call as plt.imshow(gray, cmap='gray')

# ## Ideas for Lane Detection Pipeline

# **Some OpenCV functions (beyond those introduced in the lesson) that might be useful for this project are:**
#
# `cv2.inRange()` for color selection
# `cv2.fillPoly()` for regions selection
# `cv2.line()` to draw lines on an image given endpoints
# `cv2.addWeighted()` to coadd / overlay two images
# `cv2.cvtColor()` to grayscale or change color
# `cv2.imwrite()` to output images to file
# `cv2.bitwise_and()` to apply a mask to an image
#
# **Check out the OpenCV documentation to learn about these and discover even more awesome functionality!**

# ## Helper Functions

# Below are some helper functions to help get you started. They should look familiar from the lesson!

# In[ ]:

import math

def grayscale(img):
    """Applies the Grayscale transform
    This will return an image with only one color channel
    but NOTE: to see the returned image as grayscale
    (assuming your grayscaled image is called 'gray')
    you should call plt.imshow(gray, cmap='gray')"""
    return cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    # Or use BGR2GRAY if you read an image with cv2.imread()
    # return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

def canny(img, low_threshold, high_threshold):
    """Applies the Canny transform"""
    return cv2.Canny(img, low_threshold, high_threshold)

def gaussian_blur(img, kernel_size):
    """Applies a Gaussian Noise kernel"""
    return cv2.GaussianBlur(img, (kernel_size, kernel_size), 0)

def region_of_interest(img, vertices):
    """
    Applies an image mask.

    Only keeps the region of the image defined by the polygon
    formed from `vertices`. The rest of the image is set to black.
    `vertices` should be a numpy array of integer points.
    """
    #defining a blank mask to start with
    mask = np.zeros_like(img)

    #defining a 3 channel or 1 channel color to fill the mask with depending on the input image
    if len(img.shape) > 2:
        channel_count = img.shape[2]  # i.e. 3 or 4 depending on your image
        ignore_mask_color = (255,) * channel_count
    else:
        ignore_mask_color = 255

    #filling pixels inside the polygon defined by "vertices" with the fill color
    cv2.fillPoly(mask, vertices, ignore_mask_color)

    #returning the image only where mask pixels are nonzero
    masked_image = cv2.bitwise_and(img, mask)
    return masked_image

def draw_lines(img, lines, color=None, thickness=2):
    """
    NOTE: this is the function you might want to use as a starting point once you want to
    average/extrapolate the line segments you detect to map out the full
    extent of the lane (going from the result shown in raw-lines-example.mp4
    to that shown in P1_example.mp4).

    Think about things like separating line segments by their
    slope ((y2-y1)/(x2-x1)) to decide which segments are part of the left
    line vs. the right line.  Then, you can average the position of each of
    the lines and extrapolate to the top and bottom of the lane.

    This function draws `lines` with `color` and `thickness`.
    Lines are drawn on the image inplace (mutates the image).
    If you want to make the lines semi-transparent, think about combining
    this function with the weighted_img() function below
    """
    for line in lines:
        for x1,y1,x2,y2 in line:
            #cv2.line(img, (x1, y1), (x2, y2), color, thickness)
            #cv2.line(img, (x1, y1), (x2, y2), np.random.randint(0, 256), thickness);
            #cv2.line(img, (x1, y1), (x2, y2), list(np.random.randint(0, 256, size=3, dtype=img.dtype)), thickness); # np.uint8
            #color = tuple ([int(x) for x in np.random.randint(0, 256, size=3, dtype=img.dtype)]);
            #color = np.random.randint(0, 256, size=3, dtype=int);
            if color is None:
                color = [int(x) for x in np.random.randint(0, 256, size=3, dtype=img.dtype)];
            cv2.line(img, (x1, y1), (x2, y2), color, thickness); # np.uint8

def hough_lines(img, rho, theta, threshold, min_line_len, max_line_gap): # TODO: now take out the process_lines & draw_lines_to. update the caller
    """
    `img` should be the output of a Canny transform.

    Returns an image with hough lines drawn.
    """
    lines = cv2.HoughLinesP(img, rho, theta, threshold, np.array([]), minLineLength=min_line_len, maxLineGap=max_line_gap)
    #print("len(lines) = %d"%(len(lines)));
    return lines;

def lines_process(lines, ymin, ymax, logging_str=''):
    # lines_process.dot
    # lines -> {k, b, length}
    # k -> + - each sign category:
    #   length sort two longest: keep 2x2 lines
    # : kb average
    # return two lines: kb -> xxyy with given yy; or ymin ymax of xxyy
    # if missing lanes: do nothing <- no lines returned

    # prepare
    lines_out = [];
    nline = len(lines);
    if (nline == 0):
        logging.warning("%s: nline = 0"%(logging_str));
        return;

    # k b length compute:
    k_b_length = np.zeros((3, nline)); # 3xn: 3 row matrix: k b length
    #print("lines = ");
    #print(lines);

    for iline in range(nline):
        #print("lines[iline] = " );
        #print(lines[iline]);

        for x1,y1,x2,y2 in lines[iline]:
            #print("[x1,y1,x2,y2] = [%d %d %d %d]"%(x1,y1,x2,y2));
            k = (y2-y1)/(x2-x1);
            b = y1-k*x1;
            length = np.sqrt((y2-y1)**2 + (x2-x1)**2);
            k_b_length[:, iline] = [k, b, length];

    # left:
    def longest_mean(index_selected, k_b_length):
        line = np.array([[]]);
        nselected = np.sum(index_selected);
        if (nselected == 0):
            logging.warning("%s: selected lane not detected"%(logging_str));
            return line;

        k_b_length_selected = k_b_length[:, index_selected];
        if nselected > 0 and nselected <= 2:
            k_b_length_selected_longest_mean = np.mean(k_b_length_selected, axis=1);
        else: # nline > 2
            index_longest = np.argsort(k_b_length_selected[2, :])[[-2, -1]];
            k_b_length_selected_longest_mean = np.mean(k_b_length_selected[:, index_longest], axis=1); # longest 2 lines

        line = np.array([[(ymin - k_b_length_selected_longest_mean[1])/k_b_length_selected_longest_mean[0], ymin, (ymax - k_b_length_selected_longest_mean[1])/k_b_length_selected_longest_mean[0], ymax]], dtype='int32');
        return line;

    # y = kx + b; x = (y-b)/k
    # slope classifying: left: [0] right [0]
    #print("slope = ", np.sort(k_b_length[0, :]));
    index_left = np.logical_and(k_b_length[0, :] >= -0.95, k_b_length[0, :] <= -0.60);
    index_right =  np.logical_and(k_b_length[0, :] >= 0.45, k_b_length[0, :] <= 0.75);

    line_left = longest_mean(index_left, k_b_length);
    if line_left.size > 0:
        lines_out.append(line_left);

    line_right = longest_mean(index_right, k_b_length);
    if line_right.size > 0:
        lines_out.append(line_right);

    lines_out = np.array(lines_out);
    return lines_out;

def lines_to_image(lines, imshape, color=None, thickness=2):
    line_img = np.zeros((imshape[0], imshape[1], 3), dtype=np.uint8); # img.shape
    draw_lines(line_img, lines, color=color, thickness=thickness);
    return line_img;

# Python 3 has support for cool math symbols.

def weighted_img(img, initial_img, α=0.8, β=1., γ=0.):
    """
    `img` is the output of the hough_lines(), An image with lines drawn on it.
    Should be a blank image (all black) with lines drawn on it.

    `initial_img` should be the image before any processing.

    The result image is computed as follows:

    initial_img * α + img * β + γ
    NOTE: initial_img and img must be the same shape!
    """
    return cv2.addWeighted(initial_img, α, img, β, γ)

# ## Test Images
#
# Build your pipeline to work on the images in the directory "test_images"
# **You should make sure your pipeline works well on these images before you try the videos.**

# In[ ]:

import os
image_list = [];
images_dir = 'test_images_mixed/'; # test_images
image_basename_list = [filename for filename in sorted(os.listdir(images_dir)) if '.jpg' in filename];
image_file_list = [images_dir + image_basename for image_basename in image_basename_list];
#print("image_file_list =", image_file_list);
n_image = len(image_file_list);
print("n_image = %d"%n_image);
for i_image in range(n_image):
    image_list.append(mpimg.imread(image_file_list[i_image]));
imshape = image_list[0].shape;

def plot_image_pair_4x3(image_list, image_canny_list):
    # cmap ignored for RGB(A) data so I use 'gray'
    AxesImage_canny_list = [];
    nx_sp = 3; # n_image/2;
    fig_canny_gui, ax = plt.subplots(4, 3, figsize=(15, 8), sharex=True, sharey=True);
    for i_image in range(n_image):
        #ax[0][i_image].imshow(image_blur_list[i_image], cmap='gray');
        ax[0+int(i_image/3)][i_image%3].set_title(image_file_list[i_image]);

        ax[0+int(i_image/3)][i_image%3].imshow(image_list[i_image], cmap='gray');
        AxesImage_canny_list.append(ax[2+int(i_image/3)][i_image%3].imshow(image_canny_list[i_image], cmap='gray')); #
    return fig_canny_gui, AxesImage_canny_list;

# ## Build a Lane Finding Pipeline
#
#

# Build the pipeline and run your solution on all test_images. Make copies into the `test_images_output` directory, and you can use the images in your writeup report.
#
# Try tuning the various parameters, especially the low and high Canny thresholds as well as the Hough lines parameters.

# In[ ]:

# TODO: Build your pipeline that will draw lane lines on the test_images
# then save them to the test_images_output directory.
# since later there is a process_image funcion, this can be the parameter tuning section

# ### Blur
image_blur_list = [];
for i_image in range(n_image):
    image_blur_list.append(gaussian_blur(grayscale(image_list[i_image]), 5));

# ### Canny: GUI
low_threshold_tuned, high_threshold_tuned = 42, 85; # 110, 215;
image_canny_list = image_blur_list.copy();
fig_canny_gui, AxesImage_canny_list = plot_image_pair_4x3(image_list, image_canny_list);
fig_canny_gui.suptitle('canny_gui');

def update_canny(val):
    low_threshold = slider_low_threshold.val;
    high_threshold = slider_high_threshold.val;
    for i_image in range(n_image):
        image_canny_list[i_image] = canny(image_blur_list[i_image], low_threshold, high_threshold); # 50, 150
        AxesImage_canny_list[i_image].set_data(image_canny_list[i_image]);

    fig_canny_gui.canvas.draw_idle();

plt.subplots_adjust(bottom=0.10+0.05*2); # [0.10 0.15 0.20]

ax_low_threshold = plt.axes([0.25, 0.15, 0.65, 0.03]);
slider_low_threshold = Slider(ax_low_threshold, 'low_threshold', 0, 400, valinit=low_threshold_tuned, valstep=5);
slider_low_threshold.on_changed(update_canny);

ax_high_threshold = plt.axes([0.25, 0.10, 0.65, 0.03]);
slider_high_threshold = Slider(ax_high_threshold, 'high_threshold', 0, 400, valinit=high_threshold_tuned, valstep=5);
slider_high_threshold.on_changed(update_canny);

""" parameter tuning findings:
low: < 55; otherwise P1 left lane right side will be missed
high: <280; otherwise P2 left lane right side will be missed
[55 280]
[65 195 or 215 < 225]
"""

# ### Canny: apply tuned parameter
for i_image in range(n_image):
    image_canny_list[i_image] = canny(image_blur_list[i_image], low_threshold_tuned, high_threshold_tuned); # 65, 215 # 50, 150 #  110, 215

# ### region_of_interest: vertices
# 445 -> 405: 40 pixels clearance
x_ROI_normalized = np.array([172, 405, 592, 854])/960.0;
y_ROI_normalized = np.array([347.0, 500.0])/540.0;
x_ROI = (imshape[1]*x_ROI_normalized).astype(int);
y_ROI = (imshape[0]*y_ROI_normalized).astype(int);

#y_up = 347; # 322;
#y_down = 500; # imshape[0] - 1;  # TODO: normalized
# start: lower left corner; clockwise
#vertices = np.array([[(123,y_down),(427, y_up), (550, y_up), (918,y_down)]], dtype=np.int32)
vertices = np.array([[(x_ROI[0],y_ROI[1]),(x_ROI[1], y_ROI[0]), (x_ROI[2], y_ROI[0]), (x_ROI[3],y_ROI[1])]], dtype=np.int32)
print("vertices = ", vertices);

image_vertices = image_list[2].copy();
cv2.polylines(image_vertices,[vertices],True,(0,255,255), thickness=5);
fig_v, ax_v = plt.subplots(1,1);
plt.imshow(image_vertices);
ax_v.set_title('image_vertices');

masked_edges_list = [];
for i_image in range(n_image):
    masked_edges_list.append(region_of_interest(image_canny_list[i_image], vertices));

fig_masked_edges, AxesImage_masked_list = plot_image_pair_4x3(image_list, masked_edges_list);
fig_masked_edges.suptitle('masked_edges');

# ### Hough
# #### init & plot
threshold_tuned, min_line_len_tuned, max_line_gap_tuned = 15, 15, 300; # 135; # 36, 120, 135;
image_hough_list = masked_edges_list.copy();
fig_hough_gui, AxesImage_hough_gui_list = plot_image_pair_4x3(masked_edges_list, image_hough_list);
fig_hough_gui.suptitle('hough_gui');

# #### def update_hough(val):
def update_hough(val):
    threshold = int(slider_threshold.val);
    min_line_len = slider_min_line_len.val;
    max_line_gap = slider_max_line_gap.val;

    for i_image in range(n_image):
        image_hough_list[i_image] = lines_to_image(hough_lines(masked_edges_list[i_image], 1, np.pi/180, threshold, min_line_len, max_line_gap), imshape, color=None); # 40, 20, 10); //
        AxesImage_hough_gui_list[i_image].set_data(image_hough_list[i_image]);

    fig_hough_gui.canvas.draw_idle();

plt.subplots_adjust(bottom=0.10+0.05*3); # [0.10 0.15 0.20 0.25]

ax_threshold = plt.axes([0.25, 0.20, 0.65, 0.03]);
slider_threshold = Slider(ax_threshold, 'threshold', 1, 400, valinit=threshold_tuned, valstep=5);
slider_threshold.on_changed(update_hough);

ax_min_line_len = plt.axes([0.25, 0.15, 0.65, 0.03]);
slider_min_line_len = Slider(ax_min_line_len, 'min_line_len', 0, 400, valinit=min_line_len_tuned, valstep=5);
slider_min_line_len.on_changed(update_hough);

ax_max_line_gap = plt.axes([0.25, 0.10, 0.65, 0.03]);
slider_max_line_gap = Slider(ax_max_line_gap, 'max_line_gap', 0, 300, valinit=max_line_gap_tuned, valstep=5);
slider_max_line_gap.on_changed(update_hough);

""" parameter tuning findings:
* start broad: [threshold len gap] = [1, 5(instead 1 which gives too many and distracting), longest gap between line segments ~200]
* narrow down:
        * len: increase until some line filterd out. 170
        * th: increase until critical lines get filtered out
        * gap: reduce until continuity -> broken
"""

# #### apply tuned parameters
lines_list = [];
image_line_list = [];
for i_image in range(n_image):
    lines_list.append(hough_lines(masked_edges_list[i_image], 1, np.pi/180, threshold_tuned, min_line_len_tuned, max_line_gap_tuned));
    image_line_list.append(lines_to_image(lines_list[i_image], imshape, color=None));

print("type(lines_list) = ", type(lines_list));
print("type(lines_list[0]) = ", type(lines_list[0]));
print("lines_list[0].shape = ", lines_list[0].shape);

"""
type(lines_list) =  <class 'list'>
type(lines_list[0]) =  <class 'numpy.ndarray'>
lines_list[0].shape =  (6, 1, 4)
lines_list = [ array([[[154, 538, 456, 326]], [[175, 539, 442, 338]], [[155, 538, 449, 332]], [[175, 538, 446, 334]], [[193, 510, 455, 326]], [[515, 332, 780, 491]]], dtype=int32), ...]
"""
#print("lines_list = ");
#print(lines_list);

# ### line processing
"""
lines -> [length slope:+-] -> max 2: average: [kb1 kb2] -> draw

* original
* extended to ROI top/bottom
* slope: + -: 2 categories
* reduce:
    * average
    * median of longest 2
* longest 2 averaging: -> [k b]
* draw: ROI top/bottom
"""
logging.basicConfig(format= '[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s', level=logging.INFO);
lines_processed_list = [];
for i_image in range(n_image): # [3]: #
    lines_processed_i = lines_process(lines_list[i_image], y_ROI[0], y_ROI[1], logging_str="i_image=%d"%i_image);
    print("lines_list[%d] = "%(i_image), lines_list[i_image]);
    print("lines_processed_i = ", lines_processed_i);
    lines_processed_list.append(lines_processed_i);

#print("lines_processed_list = ");
#print(lines_processed_list);

# plot image_lines_processed_list
image_lines_processed_list = [];
for i_image in range(n_image):
    image_lines_processed_list.append(lines_to_image(lines_processed_list[i_image], imshape, color=[255, 0, 0], thickness=20)); # # lines_list
fig_line_process, AxesImage_line_process_list = plot_image_pair_4x3(image_line_list, image_lines_processed_list);
fig_line_process.suptitle('line_process');

# plot and save final overlayed output: line_image
image_annotated_list = [];
images_output_dir = 'test_images_output/';
image_output_file_list = [images_output_dir + image_basename for image_basename in image_basename_list];
print("image_output_file_list = ", image_output_file_list);

for i_image in range(n_image):
    image_annotated_list.append(weighted_img(image_list[i_image], image_lines_processed_list[i_image], 0.8, 1.0, 0.));

    logging.info("i_image = %d, imsave %s"%(i_image, image_output_file_list[i_image]));
    mpimg.imsave(image_output_file_list[i_image], image_annotated_list[i_image]);
fig_annotated, _ = plot_image_pair_4x3(image_list, image_annotated_list);
fig_annotated.suptitle('annotated');

# ### check single image across pipeline
i_image = 3;
fig_pipeline, ax_pipeline = plt.subplots(2, 2, figsize=(15, 8), sharex=True, sharey=True);

ax_pipeline = ax_pipeline.ravel();

ax_pipeline[0].imshow(image_list[i_image]);
ax_pipeline[0].set_title('image');

ax_pipeline[1].imshow(image_canny_list[i_image]);
ax_pipeline[1].set_title('image_canny_list');

ax_pipeline[2].imshow(image_line_list[i_image]);
ax_pipeline[2].set_title('image_line_list');

ax_pipeline[3].imshow(image_annotated_list[i_image]);
ax_pipeline[3].set_title('image_annotated_list');

# ## Test on Videos
#
# You know what's cooler than drawing lanes over images? Drawing lanes over video!
#
# We can test our solution on two provided videos:
#
# `solidWhiteRight.mp4`
#
# `solidYellowLeft.mp4`
#
# **Note: if you get an import error when you run the next cell, try changing your kernel (select the Kernel menu above --> Change Kernel). Still have problems? Try relaunching Jupyter Notebook from the terminal prompt. Also, consult the forums for more troubleshooting tips.**
#
# **If you get an error that looks like this:**
# ```
# NeedDownloadError: Need ffmpeg exe.
# You can download it by calling:
# imageio.plugins.ffmpeg.download()
# ```
# **Follow the instructions in the error message and check out [this forum post](https://discussions.udacity.com/t/project-error-of-test-on-videos/274082) for more troubleshooting tips across operating systems.**

# In[ ]:

# Import everything needed to edit/save/watch video clips
from moviepy.editor import VideoFileClip
from IPython.display import HTML

# In[ ]:

def process_image(image):
    # NOTE: The output you return should be a color image (3 channel) for processing video below
    # you should return the final output (image where lines are drawn on lanes)

    imshape = image.shape;
    #x_ROI = (imshape[1]*np.array([172, 405, 592, 854])/960.0).astype(int); # 445 -> 405: 40 pixels clearance
    #y_ROI = (imshape[0]*np.array([347.0, 500.0])/540.0).astype(int);

    x_ROI = (imshape[1]*x_ROI_normalized).astype(int);
    y_ROI = (imshape[0]*y_ROI_normalized).astype(int);

    image_blur = gaussian_blur(grayscale(image), 5);
    image_canny = canny(image_blur, low_threshold_tuned, high_threshold_tuned);
    vertices = np.array([[(x_ROI[0],y_ROI[1]),(x_ROI[1], y_ROI[0]), (x_ROI[2], y_ROI[0]), (x_ROI[3],y_ROI[1])]], dtype=np.int32)
    masked_edges = region_of_interest(image_canny, vertices);
    lines = hough_lines(masked_edges, 1, np.pi/180, threshold_tuned, min_line_len_tuned, max_line_gap_tuned);
    lines_processed = lines_process(lines, y_ROI[0], y_ROI[1]);
    image_lines_processed = lines_to_image(lines_processed, imshape, color=[255, 0, 0], thickness=20); # # lines_list
    image_annotated = weighted_img(image, image_lines_processed, 0.8, 1.0, 0.);
    return image_annotated

# Let's try the one with the solid white lane on the right first ...

# In[ ]:

white_output = 'test_videos_output/solidWhiteRight.mp4'
## To speed up the testing process you may want to try your pipeline on a shorter subclip of the video
## To do so add .subclip(start_second,end_second) to the end of the line below
## Where start_second and end_second are integer values representing the start and end of the subclip
## You may also uncomment the following line for a subclip of the first 5 seconds
##clip1 = VideoFileClip("test_videos/solidWhiteRight.mp4").subclip(0,5)
clip1 = VideoFileClip("test_videos/solidWhiteRight.mp4")
white_clip = clip1.fl_image(process_image) #NOTE: this function expects color images!!

logging.info('write_videofile to %s start ...'%white_output);
white_clip.write_videofile(white_output, audio=False);
#get_ipython().run_line_magic('time', 'white_clip.write_videofile(white_output, audio=False)')
logging.info('write_videofile to %s end'%white_output);

# Play the video inline, or if you prefer find the video in your filesystem (should be in the same directory) and play it in your video player of choice.

# In[ ]:

HTML("""
<video width="960" height="540" controls>
  <source src="{0}">
</video>
""".format(white_output))

# ## Improve the draw_lines() function
#
# **At this point, if you were successful with making the pipeline and tuning parameters, you probably have the Hough line segments drawn onto the road, but what about identifying the full extent of the lane and marking it clearly as in the example video (P1_example.mp4)?  Think about defining a line to run the full length of the visible lane based on the line segments you identified with the Hough Transform. As mentioned previously, try to average and/or extrapolate the line segments you've detected to map out the full extent of the lane lines. You can see an example of the result you're going for in the video "P1_example.mp4".**
#
# **Go back and modify your draw_lines function accordingly and try re-running your pipeline. The new output should draw a single, solid line over the left lane line and a single, solid line over the right lane line. The lines should start from the bottom of the image and extend out to the top of the region of interest.**

# Now for the one with the solid yellow lane on the left. This one's more tricky!

# In[ ]:

yellow_output = 'test_videos_output/solidYellowLeft.mp4'
## To speed up the testing process you may want to try your pipeline on a shorter subclip of the video
## To do so add .subclip(start_second,end_second) to the end of the line below
## Where start_second and end_second are integer values representing the start and end of the subclip
## You may also uncomment the following line for a subclip of the first 5 seconds
##clip2 = VideoFileClip('test_videos/solidYellowLeft.mp4').subclip(0,5)
clip2 = VideoFileClip('test_videos/solidYellowLeft.mp4')
yellow_clip = clip2.fl_image(process_image)

#get_ipython().run_line_magic('time', 'yellow_clip.write_videofile(yellow_output, audio=False)')

logging.info('write_videofile to %s start ...'%yellow_output);
yellow_clip.write_videofile(yellow_output, audio=False);
#get_ipython().run_line_magic('time', 'yellow_clip.write_videofile(yellow_output, audio=False)')
logging.info('write_videofile to %s end'%yellow_output);

# In[ ]:

HTML("""
<video width="960" height="540" controls>
  <source src="{0}">
</video>
""".format(yellow_output))

# ## Writeup and Submission
#
# If you're satisfied with your video outputs, it's time to make the report writeup in a pdf or markdown file. Once you have this Ipython notebook ready along with the writeup, it's time to submit for review! Here is a [link](https://github.com/udacity/CarND-LaneLines-P1/blob/master/writeup_template.md) to the writeup template file.
#

# ## Optional Challenge
#
# Try your lane finding pipeline on the video below.  Does it still work?  Can you figure out a way to make it more robust?  If you're up for the challenge, modify your pipeline so it works with this video and submit it along with the rest of your project!

# In[ ]:

challenge_output = 'test_videos_output/challenge.mp4'
## To speed up the testing process you may want to try your pipeline on a shorter subclip of the video
## To do so add .subclip(start_second,end_second) to the end of the line below
## Where start_second and end_second are integer values representing the start and end of the subclip
## You may also uncomment the following line for a subclip of the first 5 seconds
##clip3 = VideoFileClip('test_videos/challenge.mp4').subclip(0,5)
clip3 = VideoFileClip('test_videos/challenge.mp4')
challenge_clip = clip3.fl_image(process_image)
#get_ipython().run_line_magic('time', 'challenge_clip.write_videofile(challenge_output, audio=False)')
logging.info('write_videofile to %s start ...'%challenge_output);
challenge_clip.write_videofile(challenge_output, audio=False);
logging.info('write_videofile to %s end'%challenge_output);

# In[ ]:

HTML("""
<video width="960" height="540" controls>
  <source src="{0}">
</video>
""".format(challenge_output))

# ## End: show
plt.show();


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     cell_metadata_filter: all
#     formats: ipynb,py:percent
#     notebook_metadata_filter: all,-language_info,-toc,-latex_envs
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.3.0
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# %% [markdown] toc=true
#
# # Plotting
#
# single: MatPlotLib single: plots
#
# The graphical representation of data---plotting---is one of the most
# important tools for evaluating and understanding scientific data and
# theoretical predictions. However, plotting is not a part of core Python
# but is provided through one of several possible library modules. The
# most highly developed and widely used plotting package for Python is
# MatPlotLib (<http://MatPlotLib.sourceforge.net/>). It is a powerful and
# flexible program that has become the *de facto* standard for 2-d
# plotting with Python.
#
# Because MatPlotLib is an external library---in fact it's a collection of
# libraries---it must be imported into any routine that uses it.
# MatPlotLib makes extensive use of NumPy so the two should be imported
# together. Therefore, for any program for which you would like to produce
# 2-d plots, you should include the lines
#
# ``` python
# import numpy as np
# import matplotlib.pyplot as plt
# ```
#
# There are other MatPlotLib sub-libraries, but the `pyplot` library
# provides nearly everything that you need for 2-d plotting. The standard
# prefix for it is `plt`. The two statements above must appear before any
# calls to NumPy or MatPlotLib routines are made.
#
# One final word before we get started: We only scratch the surface of
# what is possible using MatPlotLib and as you become familiar with it,
# you will surely want to do more than this manual describes. In that
# case, you need to go the the web to get more information. A good place
# to start is <http://matplotlib.org/api/pyplot_summary.html>. Another
# interesting web page is <http://matplotlib.org/gallery.html>.
#
# An interactive session with `pyplot`
# ------------------------------------
#
# single: plots; interactive
#
# We begin with an interactive plotting session that illustrates some very
# basic features of MatPlotLib. Type in the `plot` command shown below and
# press the return key. Take care to follow the exact syntax.
#
# ``` ipython
# In [1]: plt.plot([1,2,3,2,3,4,3,4,5])
# Out[1]: [<MatPlotLib.lines.Line2D at 0x94e1310>]
# ```
#
#

#
Interactive plot window

#

#
# A window should appear with a plot that looks something like the
# `fig-zigzagPlotDemo` shown here. By default, the `plot` function draws a
# line between the data points that were entered. You can save this plot
# to an image file by clicking on the floppy disk icon at the top of the
# plot window. You can also zoom, pan, scroll through the plot, and return
# to the original view using the other icons in the plot window.
# Experimenting with them reveals their functions.
#
# When you are finished, be sure to close the plot window.
#
# Let's take a closer look at the `plot` function. It is used to plot
# $x$-$y$ data sets and is written like this
#
# ``` python
# plt.plot(x, y)
# ```
#
# where `x` and `y` are arrays (or lists) that have the same size. If the
# `x` array is missing, that is, if there is only a single array, as in
# our example above, the `plot` function uses `0, 1, ..., N-1` for the `x`
# array, where `N` is the size of the `y` array. Thus, the `plot` function
# provides a quick graphical way of examining a data set.
#
# More typically, you supply both an $x$ and a $y$ data set to plot.
# Taking things a bit further, you may also want to plot several data sets
# on the same graph, use symbols as well as lines, label the axes, create
# a title and a legend, and control the color of symbols and lines. All of
# this is possible but requires calling a number of plotting functions.
# For this reason, plotting is usually done using a Python script or
# program.
#
# Basic plotting
# --------------
#
# single: plots; basic
#
# The quickest way to learn how to plot using the MatPlotLib library is by
# example. For our first task, let's plot the sine function over the
# interval from 0 to $4\pi$. The main plotting function `plot` in
# MatPlotLib does not plot functions *per se*, it plots $(x,y)$ data
# points. As we shall see, we can instruct the function `plot` either to
# just draw point---or dots---at each data point, or we can instruct it to
# draw straight lines between the data points. To create the illusion of
# the smooth function that the sine function is, we need to create enough
# $(x,y)$ data points so that when `plot` draws straight lines between the
# data points, the function appears to be smooth. The sine function
# undergoes two full oscillations with two maxima and two minima between 0
# and $4\pi$. So let's start by creating an array with 33 data points
# between 0 and $4\pi$, and then let MatPlotLib draw a straight line
# between them. Our code consists of four parts
#
# -   import the NumPy and MatPlotLib modules (lines 1-2 below)
# -   create the $(x,y)$ data arrays (lines 3-4 below)
# -   have `plot` draw straight lines between the $(x,y)$ data points
#     (line 5 below)
# -   display the plot in a figure window using the `show` function (line
#     6 below)
#
# Here is our code, which consists of only 6 lines:
#
# ``` python
# import numpy as np
# import matplotlib.pyplot as plt
# x = np.linspace(0, 4.*np.pi, 33)
# y = np.sin(x)
# plt.plot(x, y)
# plt.show()
# ```
#
#

#
Sine function

#

#
# Only 6 lines suffice to create the plot, which consists of the sine
# function over the interval from 0 to $4\pi$, as advertised, as well as
# axes annotated with nice whole numbers over the appropriate interval.
# It's a pretty nice plot made with very little code.
#
# One problem, however, is that while the plot oscillates like a sine
# wave, it is not smooth. This is because we did not create the $(x,y)$
# arrays with enough data points. To correct this, we need more data
# points. The plot below was created using the same program shown above
# but with 129 $(x,y)$ data points instead of 33. Try it out your self by
# copying the above program and replacing 33 in line 3 with 129 so that
# the function `linspace` creates an array with 129 data points instead of
# 33.
#
#

#
Sine function plotted using more data points

#

#
# The code above illustrates how plots can be made with very little code
# using the MatPlotLib module. In making this plot, MatPlotLib has made a
# number of choices, such as the size of the figure, the blue color of the
# line, even the fact that by default a line is drawn between successive
# data points in the $(x,y)$ arrays. All of these choices can be changed
# by explicitly instructing MatPlotLib to do so. This involves including
# more arguments in the function calls we have used and using new
# functions that control other properties of the plot. The next example
# illustrates a few of the simpler embellishments that are possible.
#
# In the `fig-WavyPulse` figure, we plot two $(x,y)$ data sets: a smooth
# line curve and some data represented by red circles. In this plot, we
# label the $x$ and $y$ axes, create a legend, and draw lines to indicate
# where $x$ and $y$ are zero. The code that creates this plot is shown
# below.
#
# ``` python
# import numpy as np
# import matplotlib.pyplot as plt
#
# # read data from file
# xdata, ydata = np.loadtxt('wavePulseData.txt', unpack=True)
#
# # create x and y arrays for theory
# x = np.linspace(-10., 10., 200)
# y = np.sin(x) * np.exp(-(x/5.0)**2)
#
# # create plot
# plt.figure(1, figsize = (6,4) )
# plt.plot(x, y, 'b-', label='theory')
# plt.plot(xdata, ydata, 'ro', label="data")
# plt.xlabel('x')
# plt.ylabel('transverse displacement')
# plt.legend(loc='upper right')
# plt.axhline(color = 'gray', zorder=-1)
# plt.axvline(color = 'gray', zorder=-1)
#
# # save plot to file
# plt.savefig('WavyPulse.pdf')
#
# # display plot on screen
# plt.show()
# ```
#
#

#
Wavy pulse

#

#
# If you have read the first four chapters, the code in lines 1-9 in the
# above script should be familiar to you. Fist, the script loads the NumPy
# and MatPlotLib modules, then reads data from a data file into two
# arrays, `xdata` and `ydata`, and then creates two more arrays, `x` and
# `y`. The first pair or arrays, `xdata` and `ydata`, contain the $x$-$y$
# data that are plotted as red circles in the `fig-WavyPulse` figure; the
# arrays created in line 8 and 9 contain the $x$-$y$ data that are plotted
# as a blue line.
#
# The functions that do the plotting begin on line 12. Let's go through
# them one by one and see what they do. You will notice in several cases
# that *keyword arguments* (`kwargs`) are used in several cases. Keyword
# arguments are *optional* arguments that have the form `kwarg=` *data*,
# where *data* might be a number, a string, a tuple, or some other form of
# data.
#
# > `figure()`
# > creates a blank figure window. If it has no arguments, it creates a
# > window that is 8 inches wide and 6 inches high by default, although
# > the size that appears on your computer depends on your screen's
# > resolution. For most computers, it will be much smaller. You can
# > create a window whose size differs from the default using the optional
# > keyword argument `figsize`, as we have done here. If you use
# > `figsize`, set it equal to a 2-element tuple where the elements are
# > the width and height, respectively, of the plot. Multiple calls to
# > `figure()` opens multiple windows: `figure(1)` opens up one window for
# > plotting, `figure(2)` another, and `figure(3)` yet another.
# >
# > `plot(x, y,` *optional arguments* `)`
# > graphs the $x$-$y$ data in the arrays `x` and `y`. The third argument
# > is a format string that specifies the color and the type of line or
# > symbol that is used to plot the data. The string `'ro'` specifies a
# > red (`r`) circle (`o`). The string `'b-'` specifies a blue (`b`) solid
# > line (`-`). The keyword argument `label` is set equal to a string that
# > labels the data if the `legend` function is called subsequently.
# >
# > `xlabel(` *string* `)`
# > takes a string argument that specifies the label for the graph's
# > $x$-axis.
# >
# > `ylabel(` *string* `)`
# > takes a string argument that specifies the label for the graph's
# > $y$-axis.
# >
# > `legend()`
# > makes a legend for the data plotted. Each $x$-$y$ data set is labeled
# > using the string that was supplied by the `label` keyword in the
# > `plot` function that graphed the data set. The `loc` keyword argument
# > specifies the location of the legend.
# >
# > `axhline()`
# > draws a horizontal line across the width of the plot at `y=0`. The
# > optional keyword argument `color` is a string that specifies the color
# > of the line. The default color is black. The optional keyword argument
# > `zorder` is an integer that specifies which plotting elements are in
# > front of or behind others. By default, new plotting elements appear
# > *on top of* previously plotted elements and have a value of
# > `zorder=0`. By specifying `zorder=-1`, the horizontal line is plotted
# > *behind* all existing plot elements that have not be assigned an
# > explicit `zorder` less than -1.
# >
# > `axvline()`
# > draws a vertical line from the top to the bottom of the plot at `x=0`.
# > See `axhline()` for explanation of the arguments.
# >
# > `savefig(` *string* `)`
# > saves the figure to data data file with a name specified by the string
# > argument. The string argument can also contain path information if you
# > want to save the file so some place other than the default directory.
# >
# > `show()`
# > displays the plot on the computer screen. No screen output is produced
# > before this function is called.
#
# single: MatPlotLib functions; figure single: MatPlotLib functions; plot
# single: MatPlotLib functions; xlabel, ylabel single: MatPlotLib
# functions; legend single: MatPlotLib functions; ayhline, axhline single:
# MatPlotLib functions; savefig single: MatPlotLib functions; show
#
# To plot the solid blue line, the code uses the `'b-'` format specifier
# in the `plot` function call. It is important to understand that
# MatPlotLib draws *straight lines* between data points. Therefore, the
# curve will appear smooth only if the data in the NumPy arrays are
# sufficiently dense. If the space between data points is too large, the
# straight lines the `plot` function draws between data points will be
# visible. For plotting a typical function, something on the order of
# 100-200 data points usually produces a smooth curve, depending on just
# how curvy the function is. On the other hand, only two points are
# required to draw a smooth straight line.
#
# Detailed information about the MatPlotLib plotting functions are
# available online, starting with the site
# <http://matplotlib.org/api/pyplot_summary.html>. The main MatPlotLib
# site is <http://matplotlib.org/>.
#
# ### Specifying line and symbol types and colors
#
# In the above example, we illustrated how to draw one line type (solid),
# one symbol type (circle), and two colors (blue and red). There are many
# more possibilities, which are specified in the tables below. The way it
# works is to specify a string consisting of one or more plotting format
# specifiers. There are two types of format specifiers, one for the line
# or symbol type and another for the color. It does not matter in which
# order the format specifiers are listed in the string. Examples are given
# following the two tables. Try them out to make sure you understand how
# these plotting format specifiers work.
#
# single: plots; line and symbol specifiers
#
# The first table below shows the characters used to specify the line or
# symbol type that is used. If a line type is chosen, the lines are drawn
# between the data points. If a marker type is chosen, the a marker is
# plotted at each data point.
#
# >

# > <colgroup>
# > <col style="width: 16%" />
# > <col style="width: 33%" />
# > <col style="width: 16%" />
# > <col style="width: 33%" />
# > </colgroup>
# >

# >

# > character
# > description
# > character
# > description
# >

# >

# >

# >

# > -
# > solid line style
# > 3
# > tri_left marker
# >

# >

# > --
# > dashed line style
# > 4
# > tri_right marker
# >

# >

# > -.
# > dash-dot line style
# > s
# > square marker
# >

# >

# > :
# > dotted line style
# > p
# > pentagon marker
# >

# >

# > .
# > point marker
# > *
# > star marker
# >

# >

# > ,
# > pixel marker
# > h
# > hexagon1 marker
# >

# >

# > o
# > circle marker
# > H
# > hexagon2 marker
# >

# >

# > v
# > triangle_down marker
# > +
# > plus marker
# >

# >

# > ^
# > triangle_up marker
# > x
# > x marker
# >

# >

# > <
# > triangle_left marker
# > D
# > diamond marker
# >

# >

# > >
# > triangle_right marker
# > d
# > thin_diamond marker
# >

# >

# > 1
# > tri_down marker
# > |
# > vline marker
# >

# >

# > 2
# > tri_up marker
# > _
# > hline marker
# >

# >

# >

# >
# This second table gives the character codes for eight different colors.
# Many more are possible but the color specification becomes more complex.
# You can consult the web-based MatPlotLib documentation for further
# details.
#
# >

# > <colgroup>
# > <col style="width: 16%" />
# > <col style="width: 16%" />
# > </colgroup>
# >

# >

# > character
# > color
# >

# >

# >

# >

# > b
# > blue
# >

# >

# > g
# > green
# >

# >

# > r
# > red
# >

# >

# > c
# > cyan
# >

# >

# > m
# > magenta
# >

# >

# > y
# > yellow
# >

# >

# > k
# > black
# >

# >

# > w
# > white
# >

# >

# >

# >
# Here are some examples of how these format specifiers can be used:
#
# ``` python
# plot(x, y, 'ro')    # plots red circles
# plot(x, y, 'ks-')   # plot black squares connected by black lines
# plot(x, y, 'g^')    # plots green triangles that point up
#
# plot(x, y, 'k-')    # plots a black line between the points
# plot(x, y, 'ms')    # plots magenta squares
# ```
#
# You can also make two calls sequentially for added versatility. For
# example, by sequentially calling the last two plot calls, the plot
# produces magenta squares on top of black lines connecting the data
# points.
#
# These format specifiers give rudimentary control of the plotting symbols
# and lines. MatPlotLib provides much more precise and detailed control of
# the plotting symbol size, line types, and colors using optional keyword
# arguments instead of the plotting format strings introduced above. For
# example, the following command creates a plot of large yellow diamond
# symbols with blue edges connected by a green dashed line:
#
# ``` python
# plt.plot(x, y, color='green', linestyle='dashed', marker='d',
#      markerfacecolor='yellow', markersize=12,
#      markeredgecolor='blue')
# ```
#
# Try it out! The online MatPlotLib documentation provides all the
# plotting format keyword arguments and their possible values.
#
# ### Error bars
#
# single: plots; error bars
#
# When plotting experimental data it is customary to include error bars
# that indicate graphically the degree of uncertainty that exists in the
# measurement of each data point. The MatPlotLib function `errorbar` plots
# data with error bars attached. It can be used in a way that either
# replaces or augments the `plot` function. Both vertical and horizontal
# error bars can be displayed. The figure below illustrates the use of
# error bars.
#
#

#
Error Bars

#

#
# When error bars are desired, you typically replace the `plot` function
# with the `errorbar` function. The first two arguments of the `errorbar`
# function are the `x` and `y` arrays to be plotted, just as for the
# `plot` function. The keyword `fmt` *must be used* to specify the format
# of the points to be plotted; the format specifiers are the same as for
# `plot`. The keywords `xerr` and `yerr` are used to specify the $x$ and
# $y$ error bars. Setting one or both of them to a constant specifies one
# size for all the error bars. Alternatively, setting one or both of them
# equal to an array that has the same length as the `x` and `y` arrays
# allows you to give each data point an error bar with a different value.
# If you only want $y$ error bars, then you should only specify the `yerr`
# keyword and omit the `xerr` keyword. The color of the error bars is set
# with the keyword `ecolor`.
#
# The code and plot below illustrates how to make error bars and was used
# to make the above plot. Lines 14 and 15 contain the call to the
# `errorbar` function. The $x$ error bars are all set to a constant value
# of 0.75, meaning that the error bars extend 0.75 to the left and 0.75 to
# the right of each data point. The $y$ error bars are set equal to an
# array, which was read in from the data file containing the data to be
# plotted, so each data point has a different $y$ error bar. By the way,
# leaving out the `xerr` keyword argument in the `errorbar` function call
# below would mean that only the $y$ error bars would be plotted.
#
# ``` python
# import numpy as np
# import matplotlib.pyplot as plt
#
# # read data from file
# xdata, ydata, yerror = np.loadtxt('expDecayData.txt', unpack=True)
#
# # create theoretical fitting curve
# x = np.linspace(0, 45, 128)
# y = 1.1+ 3.0*x*np.exp(-(x/10.0)**2)
#
# # create plot
# plt.figure(1, figsize = (6,4) )
# plt.plot(x, y, 'b-', label="theory")
# plt.errorbar(xdata, ydata, fmt='ro', label="data",
#              xerr=0.75, yerr=yerror, ecolor='black')
# plt.xlabel('x')
# plt.ylabel('transverse displacement')
# plt.legend(loc='upper right')
#
# # save plot to file
# plt.savefig('ExpDecay.pdf')
#
# # display plot on screen
# plt.show()
# ```
#
# We have more to say about the `errorbar` function in the sections on
# logarithmic plots. But the brief introduction given here should suffice
# for making most plots not involving logarithmic axes.
#
# ### Setting plotting limits and excluding data
#
# It turns out that you often want to restrict the range of numerical
# values over which you plot data or functions. In these cases you may
# need to manually specify the plotting window or, alternatively, you may
# wish to exclude data points that are outside some set of limits. Here we
# demonstrate methods for doing this.
#
# #### Setting plotting limits
#
# single: plots; setting axis limits
#
# Suppose you want to plot the tangent function over the interval from 0
# to 10. The following script offers an straightforward first attempt.
#
# ``` python
# import numpy as np
# import matplotlib.pyplot as plt
#
# theta = np.arange(0.01, 10., 0.04)
# ytan = np.tan(theta)
#
# plt.figure()
# plt.plot(theta, ytan)
# plt.show()
# ```
#
#

#
#

#
# The resulting plot, shown above, doesn't quite look like what you might
# have expected for $\tan\theta$ *vs* $\theta$. The problem is that
# $\tan\theta$ diverges at $\theta = \pi/2, 3\pi/2, 5\pi/2, ...$, which
# leads to large spikes in the plots as values in the `theta` array come
# near those values. Of course, we don't want the plot to extend all the
# way out to $\pm\infty$ in the $y$ direction, nor can it. Instead, we
# would like the plot to extend far enough that we get the idea of what is
# going on as $y\rightarrow\pm\infty$, but we would still like to see the
# behavior of the graph near $y=0$. We can restrict the range of `ytan`
# values that are plotted using the MatPlotLib function `ylim`, as we
# demonstrate in the script below.
#
# ``` python
# import numpy as np
# import matplotlib.pyplot as plt
#
# theta = np.arange(0.01, 10., 0.04)
# ytan = np.tan(theta)
#
# plt.figure()
# plt.plot(theta, ytan)
# plt.ylim(-8, 8)         # restricts range of y axis from -8 to +8
# plt.axhline(color="gray", zorder=-1)
#
# plt.show()
# ```
#
# The figure produced by this script is shown below. The plot now looks
# much more like the familiar $\tan\theta$ function we know. We have also
# include a call to the `axline` function to create an $x$ axis.
#
#

#
Tangent function (with spurious lines)

#

#
# The vertical blue lines at $\theta = \pi/2, 3\pi/2, 5\pi/2$ should not
# appear in a plot of $\tan\theta$ *vs* $\theta$. However, they do appear
# because the `plot` function simply draws lines between the data points
# in the `x`-`y` arrays provided in its arguments. Thus, `plot` draws a
# line between the very large positive and negative `ytan` values
# corresponding to the `theta` values on either side of $\pi/2$ where
# $\tan\theta$ diverges to $\pm\infty$. It would be nice to exclude that
# line.
#
# #### Masked arrays
#
# single: plots; masked arrays single: masked arrays
#
# We can exclude the data points near $\theta = \pi/2, 3\pi/2, 5\pi/2$ in
# the above plot, and thus avoid drawing the nearly vertical lines at
# those points, using NumPy's *masked array* feature. The code below shows
# how this is done and produces the graph below. The masked array feature
# is implemented in line 6 with a call to NumPy's `masked_where` function
# in the sub-module `ma` (masked array). Therefore, it is called by
# writing `np.ma.masked_where`. The `masked_where` function works as
# follows. The first argument sets the condition for masking elements of
# the array, which is specified by the second argument. In this case, the
# function says to mask all elements of the array `ytan` (the second
# argument) where the absolute value of `ytan` is greater than 20. The
# result is set equal to `ytanM`. When `ytanM` is plotted, MatPlotLib's
# `plot` function omits all masked points from the plot. You can think of
# it as the `plot` function lifting the pen that is drawing the line in
# the plot when it comes to the masked points in the array `ytanM`.
#
#

#
Tangent function

#

#
# ``` python
# import numpy as np
# import matplotlib.pyplot as plt
#
# theta = np.arange(0.01, 10., 0.04)
# ytan = np.tan(theta)
# ytanM = np.ma.masked_where(np.abs(ytan)>20., ytan)
#
# plt.figure()
# plt.plot(theta, ytanM)
# plt.ylim(-8, 8)
# plt.axhline(color="gray", zorder=-1)
#
# plt.show()
# ```
#
# ### Subplots
#
# single: plots; subplots
#
# Often you want to create two or more graphs and place them next to one
# another, generally because they are related to each other in some way.
# The plot below shows an example of such a plot. In the top graph,
# $\tan\theta$ and $\sqrt{(8/\theta)^2-1}$ *vs* $\theta$ are plotted. The
# two curves cross each other at the points where
# $\tan\theta=\sqrt{(8/\theta)^2-1}$. In the bottom $\cot\theta$ and
# $-\sqrt{(8/\theta)^2-1}$ *vs* $\theta$ are plotted. These two curves
# cross each other at the points where
# $\cot\theta=-\sqrt{(8/\theta)^2-1}$.
#
#

#
Crossing functions

#

#
# The code that produces this plot is provided below.
#
# ``` python
# import numpy as np
# import matplotlib.pyplot as plt
#
# theta = np.arange(0.01, 8., 0.04)
# y = np.sqrt((8./theta)**2-1.)
# ytan = np.tan(theta)
# ytan = np.ma.masked_where(np.abs(ytan)>20., ytan)
# ycot = 1./np.tan(theta)
# ycot = np.ma.masked_where(np.abs(ycot)>20., ycot)
#
# plt.figure(1)
#
# plt.subplot(2, 1, 1)
# plt.plot(theta, y)
# plt.plot(theta, ytan)
# plt.ylim(-8, 8)
# plt.axhline(color="gray", zorder=-1)
# plt.axvline(x=np.pi/2., color="gray", linestyle='--', zorder=-1)
# plt.axvline(x=3.*np.pi/2., color="gray", linestyle='--', zorder=-1)
# plt.axvline(x=5.*np.pi/2., color="gray", linestyle='--', zorder=-1)
# plt.xlabel("theta")
# plt.ylabel("tan(theta)")
#
# plt.subplot(2, 1, 2)
# plt.plot(theta, -y)
# plt.plot(theta, ycot)
# plt.ylim(-8, 8)
# plt.axhline(color="gray", zorder=-1)
# plt.axvline(x=np.pi, color="gray", linestyle='--', zorder=-1)
# plt.axvline(x=2.*np.pi, color="gray", linestyle='--', zorder=-1)
# plt.xlabel("theta")
# plt.ylabel("cot(theta)")
#
# plt.show()
# ```
#
# The function `subplot`, called on lines 13 and 24, creates the two
# subplots in the above figure. `subplot` has three arguments. The first
# specifies the number of rows that the figure space is to be divided
# into; on line 13, it's two. The second specifies the number of columns
# that the figure space is to be divided into; on line 13, it's one. The
# third argument specifies which rectangle the will contain the plot
# specified by the following function calls. Line 13 specifies that the
# plotting commands that follow will be act on the first box. Line 24
# specifies that the plotting commands that follow will be act on the
# second box.
#
# We have also labeled the axes and included dashed vertical lines at the
# values of $\theta$ where $\tan\theta$ and $\cot\theta$ diverge.
#
# Logarithmic plots
# -----------------
#
# single: plots; logarithmic axes
#
# Data sets can span many orders of magnitude from fractional quantities
# much smaller than unity to values much larger than unity. In such cases
# it is often useful to plot the data on logarithmic axes.
#
# ### Semi-log plots
#
# single: plots; semi-log
#
# For data sets that vary exponentially in the independent variable, it is
# often useful to use one or more logarithmic axes. Radioactive decay of
# unstable nuclei, for example, exhibits an exponential decrease in the
# number of particles emitted from the nuclei as a function of time. In
# the plot below, for example, we show the decay of the radioactive
# isotope Phosphorus-32 over a period of 6 months, where the radioactivity
# is measured once each week. Starting at a decay rate of nearly $10^4$
# electrons (counts) per second, the decay rate diminishes to only about 1
# count per second after about 6 months or 180 days. If we plot counts per
# second as a function of time on a normal plot, as we have done in the
# plot on the left below, then the count rate is indistinguishable from
# zero after about 100 days. On the other hand, if we use a logarithmic
# axis for the count rate, as we have done in the plot on the right below,
# then we can follow the count rate well past 100 days and can readily
# distinguish it from zero. Moreover, if the data vary exponentially in
# time, then the data will fall along a straight line, as they do for the
# case of radioactive decay.
#
#

#
Semi-log plotting

#

#
# MatPlotLib provides two functions for making semi-logarithmic plots,
# `semilogx` and `semilogy`, for creating plots with logarithmic $x$ and
# $y$ axes, with linear $y$ and $x$ axes, respectively. We illustrate
# their use in the program below, which made the above plots.
#
# ``` python
# import numpy as np
# import matplotlib.pyplot as plt
#
# # read data from file
# time, counts, unc = np.loadtxt('SemilogDemo.txt', unpack=True)
#
# # create theoretical fitting curve
# tau = 20.2      # Phosphorus-32 half life = 14 days; tau = t_half/ln(2)
# N0 = 8200.       # Initial count rate (per second)
# t = np.linspace(0, 180, 128)
# N = N0 * np.exp(-t/tau)
#
# # create plot
# plt.figure(1, figsize = (10,4) )
#
# plt.subplot(1, 2, 1)
# plt.plot(t, N, 'b-', label="theory")
# plt.plot(time, counts, 'ro', label="data")
# plt.xlabel('time (days)')
# plt.ylabel('counts per second')
# plt.legend(loc='upper right')
#
# plt.subplot(1, 2, 2)
# plt.semilogy(t, N, 'b-', label="theory")
# plt.semilogy(time, counts, 'ro', label="data")
# plt.xlabel('time (days)')
# plt.ylabel('counts per second')
# plt.legend(loc='upper right')
#
# plt.tight_layout()
#
# # display plot on screen
# plt.show()
# ```
#
# The `semilogx` and `semilogy` functions work the same way as the `plot`
# function. You just use one or the other depending on which axis you want
# to be logarithmic.
#
# #### The `tight_layout()` function
#
# single: MatPlotLib functions; tight\_layout
#
# You may have noticed the `tight_layout()` function, called without
# arguments on line 30 of the program. This is a convenience function that
# adjusts the sizes of the plots to make room for the axes labels. If it
# is not called, the $y$-axis label of the right plot runs into the left
# plot. The `tight_layout()` function can also be useful in graphics
# windows with only one plot sometimes.
#
# ### Log-log plots
#
# single: plots; log-log
#
# MatPlotLib can also make log-log or double-logarithmic plots using the
# function `loglog`. It is useful when both the $x$ and $y$ data span many
# orders of magnitude. Data that are described by a power law $y=Ax^b$,
# where $A$ and $b$ are constants, appear as straight lines when plotted
# on a log-log plot. Again, the `loglog` function works just like the
# `plot` function but with logarithmic axes.
#
# More advanced graphical output
# ------------------------------
#
# The plotting methods introduced in the previous sections are perfectly
# adequate for basic plotting and are therefore recommended for simple
# graphical output. Here, we introduce an alternative syntax that
# harnesses the full power of MatPlotLib. It gives the user more options
# and greater control. Perhaps the most efficient way to learn this
# alternative syntax is to look at an example. The figure below
# illustrating `MultPlotDemo` is produced by the following code:
#
#

#
Mulitple plots in the same window

#

#
#     # Demonstrates the following:
#     #     plotting logarithmic axes
#     #     user-defined functions
#     #     "where" function, NumPy array conditional
#
#     import numpy as np
#     import matplotlib.pyplot as plt
#
#     # Define the sinc function, with output for x=0 defined
#     # as a special case to avoid division by zero. The code
#     # below defining the sinc function is developed and
#     # explained in Chapter 7, Section 1.
#     def s(x):
#       a = np.where(x==0., 1., np.sin(x)/x)
#       return a
#
#     # create arrays for plotting
#     x = np.arange(0., 10., 0.1)
#     y = np.exp(x)
#
#     t = np.linspace(-10., 10., 100)
#     z = s(t)
#
#     # create a figure window
#     fig = plt.figure(1, figsize=(9,8))
#
#     # subplot: linear plot of exponential
#     ax1 = fig.add_subplot(2,2,1)
#     ax1.plot(x, y)
#     ax1.set_xlabel('time (ms)')
#     ax1.set_ylabel('distance (mm)')
#     ax1.set_title('exponential')
#
#     # subplot: semi-log plot of exponential
#     ax2 = fig.add_subplot(2,2,2)
#     ax2.plot(x, y)
#     ax2.set_yscale('log')
#     ax2.set_xlabel('time (ms)')
#     ax2.set_ylabel('distance (mm)')
#     ax2.set_title('exponential')
#
#     # subplot: wide subplot of sinc function
#     ax3 = fig.add_subplot(2,1,2)
#     ax3.plot(t, z, 'r')
#     ax3.axhline(color='gray')
#     ax3.axvline(color='gray')
#     ax3.set_xlabel('angle (deg)')
#     ax3.set_ylabel('electric field')
#     ax3.set_title('sinc function')
#
#     # Adjusts white space to avoid collisions between subplots
#     fig.tight_layout()
#     plt.show()
#
# After defining several arrays for plotting, the above program opens a
# figure window in line 23 with the statement :
#
#     fig = plt.figure(figsize=(9,8))
#
# The MatPlotLib statement above creates a **Figure** object, assigns it
# the name `fig`, and opens a blank figure window. Thus, just as we give
# lists, arrays, and numbers variable names (*e.g.* `a = [1, 2, 5, 7]`,
# `dd = np.array([2.3, 5.1, 3.9])`, or `st = 4.3`), we can give a figure
# object and the window in creates a name: here it is `fig`. In fact we
# can use the `figure` function to open up multiple figure objects with
# different figure windows. The statements :
#
#     fig1 = plt.figure()
#     fig2 = plt.figure()
#
# open up two separate windows, one named `fig1` and the other `fig2`. We
# can then use the names `fig1` and `fig2` to plot things in either
# window. The `figure` function need not take any arguments if you are
# satisfied with the default settings such as the figure size and the
# background color. On the other hane, by supplying one or more keyword
# arguments, you can customize the figure size, the background color, and
# a few other properties. For example, in the program listing (line 23),
# the keyword argument `figsize` sets the width and height of the figure
# window; the default size is `(8, 6)`; in our program we set it to
# `(9, 8)`, which is a bit wider and higher than the default size. In the
# example above, we also choose to open only a single window, hence the
# single `figure` call.
#
# The `fig.add_subplot(2,2,1)` in line 30 is a MatPlotLib function that
# divides the figure window into 2 rows (the first argument) and 2 columns
# (the second argument). The third argument creates a subplot in the first
# of the 4 subregions (*i.e.* of the 2 rows $\times$ 2 columns) created by
# the `fig.add_subplot(2,2,1)` call. To see how this works, type the
# following code into a Python module and run it:
#
#     import numpy as np
#     import matplotlib.pyplot as plt
#
#     fig = plt.figure(figsize=(9,8))
#     ax1 = fig.add_subplot(2,2,1)
#
#     plt.show()
#
# You should get a figure window with axes drawn in the upper left
# quadrant. The `fig.` prefix used with the `add_subplot(2,2,1)` function
# directs Python to draw these axes in the figure window named `fig`. If
# we had opened two figure windows, changing the prefix to correspond to
# the name of one or the other of the figure windows would direct the axes
# to be drawn in the appropriate window. Writing
# `ax1 = fig.add_subplot(2,2,1)` assigns the name ax1 to the axes in the
# upper left quadrant of the figure window.
#
# The `ax1.plot(x, y)` in line 27 directs Python to plot the
# previously-defined `x` and `y` arrays onto the axes named `ax1`. The
# `ax2 = fig.add_subplot(2,2,2)` draws axes in the second, or upper right,
# quadrant of the figure window. The `ax3 = fig.add_subplot(2,1,2)`
# divides the figure window into 2 rows (first argument) and 1 column
# (second argument), creates axes in the second or these two sections, and
# assigns those axes (*i.e.* that subplot) the name `ax3`. That is, it
# divides the figure window into 2 halves, top and bottom, and then draws
# axes in the half number 2 (the third argument), or lower half of the
# figure window.
#
# You may have noticed in above code that some of the function calls are a
# bit different from those used before: `xlabel(’time (ms)’)` becomes
# `set_xlabel(’time (ms)’)`, `title(’exponential’)` becomes
# `set_title(’exponential’)`, *etc.*
#
# The call `ax2.set_yscale('log')` sets the $y$-axes in the second plot to
# be logarithmic, thus creating a semi-log plot. Creating properly-labeled
# logarthmic axes like this is more straightforward with the advanced
# syntax illustrated in the above example.
#
# Using the prefixes `ax1`, `ax2`, or `ax3`, direct graphical instructions
# to their respective subplots. By creating and specifying names for the
# different figure windows and subplots within them, you access the
# different plot windows more efficiently. For example, the following code
# makes four identical subplots in a single figure window using a `for`
# loop.
#
# ``` ipython
# In [1]: fig = figure()
#
# In [2]: ax1 = fig.add_subplot(221)
#
# In [3]: ax2 = fig.add_subplot(222)
#
# In [4]: ax3 = fig.add_subplot(223)
#
# In [5]: ax4 = fig.add_subplot(224)
#
# In [6]: for ax in [ax1, ax2, ax3, ax4]:
#    ...:     ax.plot([3,5,8],[6,3,1])
#
# In [7]: show()
# ```
#
# Exercises
# ---------
#
# 1.  Plot the function $y=3x^2$ for $-1 \le x \le 3$ as a continuous
#     line. Include enough points so that the curve you plot appears
#     smooth. Label the axes $x$ and $y$.
#
# 2.  Plot the following function for $-15 \le x \le 15$:
#
#     $$y = \frac{\cos x}{1+\frac{1}{5}x^2}$$
#
#     Include enough points so that the curve you plot appears smooth.
#     Label the axes $x$ and $y$.
#
# 3.  Plot the functions $\sin x$ and $\cos x$ *vs* $x$ on the same plot
#     with $x$ going from $-\pi$ to $\pi$. Make sure the limits of
#     $x$-axis do not extend beyond the limits of the data. Plot $\sin x$
#     in the color green and $\cos x$ in the color black and include a
#     legend to label the two curves. Place the legend within the plot,
#     but such that it does not cover either of the sine or cosine traces.
#
# 4.  Create a data file with the data shown below.
#
#     1.  Read the data into Python program and plot $t$ *vs* $y$ using
#         circles for data points with error bars. Use the data in the
#         `dy` column as the error estimates for the $y$ data. Label the
#         horizontal and vertical axes "time (s)" and "position (cm)".
#
#     2.  On the same graph, plot the function below as a smooth line.
#         Make the line pass *behind* the data points.
#
#         $$y(t) = \left[3 +
#                \frac{1}{2}\sin\frac{\pi t}{5}\right]
#                t\, e^{-t/10}$$
#
#             Data for Exercise 4
#             Date: 16-Aug-2013
#             Data taken by Lauren and John
#
#              t      d       dy
#              1.0    2.94    0.7
#              4.5    8.29    1.2
#              8.0    9.36    1.2
#             11.5   11.60    1.4
#             15.0    9.32    1.3
#             18.5    7.75    1.1
#             22.0    8.06    1.2
#             25.5    5.60    1.0
#             29.0    4.50    0.8
#             32.5    4.01    0.8
#             36.0    2.62    0.7
#             39.5    1.70    0.6
#             43.0    2.03    0.6
#
# 5.  Use MatPlotLib's function `hist` along with NumPy's function's
#     `random.rand` and `random.randn` to create the histogram graphs
#     shown in Fig. `fig-randhistos`
#
# 6.  Plot force *vs* distance with error bars using the following data:
#
#         d=np.array([0.38, 0.64, 0.91, 1.26, 1.41, 1.66, 1.90, 2.18])
#         f=np.array([1.4, 1.65, 3.0, 3.95, 4.3, 5.20, 6.85, 7.4])
#         df=np.array([ 0.4, 0.5, 0.4, 0.5, 0.6, 0.5, 0.5, 0.4])
#
#     Your plot should also include a visual straight "best fit" to the
#     data as well as visual "fits" that give the smallest and largest
#     slopes consistent with the data. Note, you only need two points to
#     define a straight line so the straight lines you draw on the plot
#     should be arrays of length 2 and no longer. All of your fitted lines
#     should lie *behind* the data. Try to make your plot look like the
#     one below. *In addition*, add a legend to your plot the gives the
#     slope with its uncertainty obtained from your visual fits to the
#     data.
#
#

#
#

#
#     The web page <http://matplotlib.org/api/pyplot_summary.html> gives a
#     summary of the main plotting commands available in MatPlotLib. The
#     two important ones here are `plot` and `errorbar`, which make
#     regular plots and plots with error bars, respectively. You will find
#     the following keyword arguments useful: `yerr`, `ls`, `marker`,
#     `mfc`, `mec`, `ms`, and `ecolor`, which you can find described by
#     clicking on the `errorbar` function link on the web page cited
#     above.
#
# 7.  The data file below shows data obtained for the displacement
#     (position) *vs* time of a falling object, together with the
#     estimated uncertainty in the displacement.
#
#     >     Measurements of fall velocity vs time
#     >     Taken by A.P. Crawford and S.M. Torres
#     >     19-Sep-13
#     >     time (s)    position (m)    uncertainty (m)
#     >      0.0            0.0             0.04
#     >      0.5            1.3             0.12
#     >      1.0            5.1             0.2
#     >      1.5           10.9             0.3
#     >      2.0           18.9             0.4
#     >      2.5           28.7             0.4
#     >      3.0           40.3             0.5
#     >      3.5           53.1             0.6
#     >      4.0           67.5             0.6
#     >      4.5           82.3             0.6
#     >      5.0           97.6             0.7
#     >      5.5          113.8             0.7
#     >      6.0          131.2             0.7
#     >      6.5          148.5             0.7
#     >      7.0          166.2             0.7
#     >      7.5          184.2             0.7
#     >      8.0          201.6             0.7
#     >      8.5          220.1             0.7
#     >      9.0          238.3             0.7
#     >      9.5          256.5             0.7
#     >     10.0          275.6             0.8
#
#     1.  Use these data to calculate the velocity and acceleration (in a
#         Python program `.py` file), together with their uncertainties
#         propagated from the displacement *vs* time uncertainties. Be
#         sure to calculate time arrays corresponding the midpoint in time
#         between the two displacements or velocities for the velocity and
#         acceleration arrays, respectively.
#     2.  In a single window frame, make three vertically stacked plots of
#         the displacement, velocity, and acceleration *vs* time. Show the
#         error bars on the different plots. Make sure that the time axes
#         of all three plots cover the same range of times. Why do the
#         relative sizes of the error bars grow progressively greater as
#         one progresses from displacement to velocity to acceleration?

# %%


"""
Imperative Programming
======================

**Author**: Yi-Hsiang Lai (seanlatias@github)

There exist many applications that cannot be described using only vectorized
code such as `hcl.compute`. Thus, we introduce imperative programming in
HeteroCL, which makes HeteroCL applications more expressive. In this tutorial,
we will implement *insertion sort* in HeteroCL.
"""

import heterocl as hcl

hcl.init()

A = hcl.placeholder((10,), "A")

##############################################################################
# Stages in HeteroCL
# ------------------
# In HeteroCL, when users write an application, they are actually building a
# compute graph. Each node in a graph is a *stage*. Each edge is directed,
# which represents the data flow between two stages. Some HeteroCL APIs
# naturally form a stage, such as ``hcl.compute``. Since the imperative code
# we are going to write cannot be described using a HeteroCL API, we need to
# wrap it as a stage explicitly via ``hcl.Stage``. Users can specify the name
# of a stage, which is optional. Note that **a HeteroCL application must have
# at least one stage**.

def insertion_sort(A):

    # Introduce a stage.
    with hcl.Stage("S"):
        # for i in range(1, A.shape[0])
        # We can name the axis
        with hcl.for_(1, A.shape[0], name="i") as i:
            key = hcl.local(A[i], "key")
            j = hcl.local(i-1, "j")
            # while(j >= 0 && key < A[j])
            with hcl.while_(hcl.and_(j >= 0, key < A[j])):
                A[j+1] = A[j]
                j[0] -= 1
            A[j+1] = key[0]

##############################################################################
# Imperative DSL
# --------------
# To write imperative code in HeteroCL, we need to use a subset of HeteroCL
# DSL, which is *imperative DSL*. HeteroCL's imperative DSL supports a subset
# of Python's control flow statements, including conditional statements and
# control flows. In the above code, we show how we can use ``hcl.for_`` to
# write a `for` loop and ``hcl.while_`` to write a `while` loop. Moreover, we
# use ``hcl.and_`` for logical expressions. Here we also introduce a new API,
# which is ``hcl.local``. It is equivalent to
#
# ``hcl.compute((1,))``
#
# Namely, it declares a tensor with exactly one element, which can be treated
# as a **stateful scalar**. Following we show the execution results of the
# implemented sorting algorithm.
#
# .. note::
#
#    Currently we support the following imperative DSLs. Logic operations:
#    :obj:`heterocl.and_`, :obj:`heterocl.or_`. Control flow statements:
#    :obj:`heterocl.if_`, :obj:`heterocl.else_`, :obj:`heterocl.elif_`,
#    :obj:`heterocl.for_`, :obj:`heterocl.while_`, :obj:`heterocl.break_`.

s = hcl.create_schedule([A], insertion_sort)

##############################################################################
# We can inspect the generated IR.
print(hcl.lower(s))

##############################################################################
# Finally, we build the executable and feed it with Numpy arrays.
f = hcl.build(s)

import numpy as np

hcl_A = hcl.asarray(np.random.randint(50, size=(10,)))

print('Before sorting:')
print(hcl_A)

f(hcl_A)

print('After sorting:')
np_A = hcl_A.asnumpy()
print(np_A)

##############################################################################
# Let's run some tests for verification.
for i in range(1, 10):
    assert np_A[i] >= np_A[i-1]

##############################################################################
# Bit Operations
# --------------
# HeteroCL also support bit operations including setting/getting a bit/slice
# from a number. This is useful for integer and fixed-point operations.
# Following we show some basic examples.
hcl.init()
A = hcl.placeholder((10,), "A")
def kernel(A):
    # get the LSB of A
    B = hcl.compute(A.shape, lambda x: A[x][0], "B")
    # get the lower 4-bit of A
    C = hcl.compute(A.shape, lambda x: A[x][4:0], "C")
    return B, C

##############################################################################
# Note that for the slicing operations, we follow the convention of Python,
# which is **left exclusive and right inclusive**. Now we can test the results.
s = hcl.create_schedule(A, kernel)
f = hcl.build(s)

np_A = np.random.randint(0, 100, A.shape)
hcl_A = hcl.asarray(np_A)
hcl_B = hcl.asarray(np.zeros(A.shape))
hcl_C = hcl.asarray(np.zeros(A.shape))

f(hcl_A, hcl_B, hcl_C)

print("Input array:")
print(hcl_A)
print("Least-significant bit:")
print(hcl_B)
print("Lower four bits:")
print(hcl_C)

# a simple test
np_B = hcl_B.asnumpy()
np_C = hcl_C.asnumpy()
for i in range(0, 10):
    assert np_B[i] == np_A[i] % 2
    assert np_C[i] == np_A[i] % 16

##############################################################################
# The operations for bit/slice setting is similar. The only difference is that
# we need to use imperative DSL. Following is an example.
hcl.init()
A = hcl.placeholder((10,), "A")
B = hcl.placeholder((10,), "B")
C = hcl.placeholder((10,), "C")
def kernel(A, B, C):
    with hcl.Stage("S"):
        with hcl.for_(0, 10) as i:
            # set the LSB of B to be the same as A
            B[i][0] = A[i][0]
            # set the lower 4-bit of C
            C[i][4:0] = A[i]

s = hcl.create_schedule([A, B, C], kernel)
f = hcl.build(s)
# note that we intentionally limit the range of A
np_A = np.random.randint(0, 16, A.shape)
np_B = np.random.randint(0, 100, A.shape)
np_C = np.random.randint(0, 100, A.shape)
hcl_A = hcl.asarray(np_A)
hcl_B = hcl.asarray(np_B)
hcl_C = hcl.asarray(np_C)

f(hcl_A, hcl_B, hcl_C)

print("Input array:")
print(hcl_A)
print("Before setting the least-significant bit:")
print(np_B)
print("After:")
print(hcl_B)
print("Before setting the lower four bits:")
print(np_C)
print("After:")
print(hcl_C)

# let's do some checks
np_B2 = hcl_B.asnumpy()
np_C2 = hcl_C.asnumpy()
for i in range(0, 10):
    assert np_B2[i] % 2 == np_A[i] % 2
    assert np_B2[i] // 2 == np_B[i] // 2
    assert np_C2[i] % 16 == np_A[i]
    assert np_C2[i] // 16 == np_C[i] // 16