From 0a892bbee7487478dc5a1c5f1ebe729157f3a74c Mon Sep 17 00:00:00 2001 From: raynardj Date: Thu, 23 Dec 2021 13:49:28 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=AF=E2=80=8D=E2=99=80=EF=B8=8F=20inter?= =?UTF-8?q?active=20enhacne?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/_data/sidebars/home_sidebar.yml | 7 +- docs/html.html | 101 +++- docs/image_widgets.html | 418 +++++++++++++++++ docs/inter_widgets.html | 205 ++++++++- docs/multiprocess.html | 269 +++++++++++ docs/sidebar.json | 3 +- forgebox/__init__.py | 2 +- forgebox/_nbdev.py | 18 +- forgebox/html.py | 59 ++- forgebox/images/widgets.py | 30 +- forgebox/widgets.py | 335 +++++++++++++- nbs/04_html.ipynb | 77 +++- nbs/05_inter_widgets.ipynb | 431 +++++++++++++++++- ...1_widgets.ipynb => 51_image_widgets.ipynb} | 50 +- settings.ini | 2 +- 15 files changed, 1876 insertions(+), 131 deletions(-) create mode 100644 docs/image_widgets.html create mode 100644 docs/multiprocess.html rename nbs/{51_widgets.ipynb => 51_image_widgets.ipynb} (98%) diff --git a/docs/_data/sidebars/home_sidebar.yml b/docs/_data/sidebars/home_sidebar.yml index d50c1ff..4797e03 100644 --- a/docs/_data/sidebars/home_sidebar.yml +++ b/docs/_data/sidebars/home_sidebar.yml @@ -36,6 +36,9 @@ entries: - output: web,pdf title: FreeMap url: freemap.html + - output: web,pdf + title: Multiprocessing Helper + url: multiprocess.html - output: web,pdf title: Controllable loop process url: loop.html @@ -67,8 +70,8 @@ entries: title: Wild Tree and loss url: wild_tree_and_loss.html - output: web,pdf - title: Images - url: widgets.html + title: Image Widgets + url: image_widgets.html - output: web,pdf title: Categorical Transformation for DL url: category.html diff --git a/docs/html.html b/docs/html.html index 4bf759d..f1e6765 100644 --- a/docs/html.html +++ b/docs/html.html @@ -40,7 +40,7 @@
-

class DOM[source]

DOM(txt, tag, kwargs={})

+

class DOM[source]

DOM(txt, tag, kwargs={})

@@ -353,7 +353,94 @@

DOM operation -

deeper[source]

deeper(x)

+

image_to_base64[source]

image_to_base64(img:Image)

+
+

Transform PIL Image to base64 for API +Return:

+ +
- base64 encoded image bytes
+ + + + + + + + + + {% endraw %} + + {% raw %} + +
+ +
+
+ +
+ + +
+

data_url[source]

data_url(img:Image)

+
+

Return:

+ +
- data url string,
+    can be used as the src value of <img>
+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+
+ +
+ + +
+

img_dom[source]

img_dom(img:Image)

+
+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+
+ +
+ + +
+

deeper[source]

deeper(x)

@@ -377,7 +464,7 @@

deeper -

list_group[source]

list_group(iterable)

+

list_group[source]

list_group(iterable)

@@ -408,7 +495,7 @@

list_group -

col_sm[source]

col_sm(iterable, portions=None)

+

col_sm[source]

col_sm(iterable, portions=None)

@@ -503,7 +590,7 @@

col_sm -

list_group_kv[source]

list_group_kv(data)

+

list_group_kv[source]

list_group_kv(data)

@@ -654,7 +741,7 @@

A class designed for running ja
-

JS[source]

JS(code)

+

JS[source]

JS(code)

@@ -678,7 +765,7 @@

JS -

JS_file[source]

JS_file(path)

+

JS_file[source]

JS_file(path)

load javascript file

diff --git a/docs/image_widgets.html b/docs/image_widgets.html new file mode 100644 index 0000000..7cbcd95 --- /dev/null +++ b/docs/image_widgets.html @@ -0,0 +1,418 @@ +--- + +title: Image Widgets + + +keywords: fastai +sidebar: home_sidebar + +summary: "Extra widgets for jupyter noteook" +description: "Extra widgets for jupyter noteook" +nb_path: "nbs/51_image_widgets.ipynb" +--- + + +
+ + {% raw %} + +
+ +
+ {% endraw %} + +
+
+

Imports

+
+
+
+ {% raw %} + +
+ +
+ {% endraw %} + +
+
+

Data url for img tag

+
+
+
+
+
+

Preview images in jupyter notebook

+
+
+
+ {% raw %} + +
+ +
+
+ +
+ + +
+

image_dom[source]

image_dom(img:PIL.Image, **kwargs)

+
+

Create tag with src='data:...' + with PIL.Image object +return forgebox.html.DOM

+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+
+ +
+ + +
+

with_title_dom[source]

with_title_dom(img:PIL.Image, title:str, col:int)

+
+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+
+ +
+ + +
+

view_images[source]

view_images(*images, num_per_row:int=4, titles:List[T]=None)

+
+

Create

wraping up images +view_images( + img1, img2, img3, + img4, img5, img6, + img7)()

+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+ {% endraw %} + +
+
+

Subplot

+
+
+
+ {% raw %} + +
+ +
+
+ +
+ + +
+

class Subplots[source]

Subplots(total:int, ncol:int=3, figsize=None)

+
+

Simplifying plt sublots +sub = Subplots(18)

+ +
@sub.single
+def plotting(ax, data):
+    ax.plot(data)
+
+for data in data_list:
+    sub(data)
+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+ {% endraw %} + +
+
+

Test on image preview

+
+
+
+ {% raw %} + +
+
+ +
+
+
import os
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

Original image

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
HOME = os.environ['HOME']
+img = Image.open(f"{HOME}/Pictures/img02.jpg").resize((200,50))
+img
+
+ +
+
+
+ +
+
+ +
+ + + +
+ +
+ +
+ +
+
+ +
+ {% endraw %} + +
+
+

Data url of the image

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
data_url(img)[:100]
+
+ +
+
+
+ +
+
+ +
+ + + +
+
'data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHB'
+
+ +
+ +
+
+ +
+ {% endraw %} + +
+
+

Visualize single image

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
image_dom(img)()
+
+ +
+
+
+ +
+
+ +
+ + +
+
+
+ +
+ +
+
+ +
+ {% endraw %} + +
+
+

Visualize serveral images

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
view_images(img, img, img, img, img, img, titles=list(f"lake:{i}" for i in range(6)))()
+
+ +
+
+
+ +
+
+ +
+ + +
+
lake:0
lake:1
lake:2
lake:3
lake:4
lake:5
+
+ +
+ +
+
+ +
+ {% endraw %} + +
+
+

Contains more images in a row

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
view_images(
+    img, img, img, img, img, img,
+    num_per_row=6)()
+
+ +
+
+
+ +
+
+ +
+ + +
+
+
+ +
+ +
+
+ +
+ {% endraw %} + +
+ + diff --git a/docs/inter_widgets.html b/docs/inter_widgets.html index 3a3bdc0..a046324 100644 --- a/docs/inter_widgets.html +++ b/docs/inter_widgets.html @@ -47,7 +47,7 @@
-

display_df[source]

display_df(df)

+

display_df[source]

display_df(df)

@@ -71,7 +71,7 @@

display_df -

search_box(df, columns, manual=False, max_rows=10, callback=display_df)

+

search_box(df, columns, manual=False, max_rows=10, callback=display_df)

create a search box based on dataframe df: pandas dataframe @@ -188,7 +188,7 @@

Interactive pagination -

paginate[source]

paginate(df, page_len=20)

+

paginate[source]

paginate(df, page_len=20)

Paginate dataframe in jupyter notebook interactively Like you can flip through the page

@@ -260,7 +260,7 @@

Example

-

make_hboxes[source]

make_hboxes(*widgets, sections:int=2)

+

make_hboxes[source]

make_hboxes(*widgets, sections:int=2)

Make a list of HBox, with each hbox contans {sections} of widgets at most @@ -288,7 +288,7 @@

make_hboxes -

class SingleButton[source]

SingleButton(btn_text:str='Run', btn_style:str='danger', callback:Callable=<lambda>, sections:int=2)

+

class SingleButton[source]

SingleButton(btn_text:str='Run', btn_style:str='danger', callback:Callable=<lambda>, sections:int=2)

A single button widget It's like interactive_manual with callback

@@ -399,7 +399,7 @@

Dataframe labeler -

class Labeler[source]

Labeler(df:DataFrame, options_col:str, result_col:str='label', show_callback:Callable=None, auto_fill_in:bool=True)

+

class Labeler[source]

Labeler(df:DataFrame, options_col:str, result_col:str='label', show_callback:Callable=None, auto_fill_in:bool=True)

An interactive tool labeling pandas dataframe row by row

@@ -784,9 +784,202 @@

Review the label result

+

+ {% endraw %} + + + {% raw %} + +
+ +
+
+ +
+ + +
+

class EditableList[source]

EditableList(*args, **kwargs) :: VBox

+
+

Interactive list +You can add item to the list +Each added item has a remove button to remove such item

+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+
+ +
+ + +
+

class EditableDict[source]

EditableDict(*args, **kwargs) :: VBox

+
+

Interactive dictionary +You can add item to the dictionary +Each added item has a remove button to remove such item

+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+ {% endraw %} + +
+
+

Step by step

+
+
+
+ {% raw %} + +
+ +
+
+ +
+ + +
+

class LivingStep[source]

LivingStep(func:Callable, top_block:HTML=None)

+
+

A step interactive for StepByStep

+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+
+ +
+ + +
+

class StepByStep[source]

StepByStep(funcs:Dict[str, Callable], top_board:HTML=None, kwargs:Dict[str, Any]={})

+
+

A tool to manage progress step by step

+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
def test_step(**kwargs):
+    @interact
+    def show_step_(how_much=[1,2,4,8,16], charactor = ['šŸŗ', 'šŸŗ', 'šŸ¶','šŸ·']):
+        return pd.DataFrame({"stuff":[charactor*how_much,]*how_much})
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
sbs = StepByStep(
+    {"step1":test_step,"some_step":test_step, "another_step":test_step},
+    HTML("""
+    <h1>Detail Steps</h1>
+    """)
+)
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
sbs()
+
+ +
+
+
+
{% endraw %}

+ + diff --git a/docs/multiprocess.html b/docs/multiprocess.html new file mode 100644 index 0000000..d163ad2 --- /dev/null +++ b/docs/multiprocess.html @@ -0,0 +1,269 @@ +--- + +title: Multiprocessing Helper + + +keywords: fastai +sidebar: home_sidebar + +summary: "Things to smooth up the multiprocessing" +description: "Things to smooth up the multiprocessing" +nb_path: "nbs/09_multiprocess.ipynb" +--- + + +
+ + {% raw %} + +
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+ {% endraw %} + +
+
+

Separate Process Magic

+
+
+
+ {% raw %} + +
+ +
+
+ +
+ + +
+

class VipaClass[source]

VipaClass()

+
+

From meditation word Vipassana +Where creating a magic function +you can run a cell without holding the entire notebook

+
%%vipas
+...do things
+
+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+ {% endraw %} + +
+
+

Read single file line by line

+
+
+
+ {% raw %} + +
+ +
+
+ +
+ + +
+

class SingleFileLiner[source]

SingleFileLiner(file_path:Path, total:int=None)

+
+

Text data reading line by line for multiprocessing

+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
sfl = SingleFileLiner("../README.md")
+from joblib import Parallel, delayed
+from time import sleep
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
def get_line(x):
+#     sleep(1)
+    return x
+
+res = Parallel(backend="multiprocessing", n_jobs=6)(delayed(get_line)(i) for i in sfl)
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

Read dataframe row by row

+
+
+
+ {% raw %} + +
+ +
+
+ +
+ + +
+

class DataFrameRowling[source]

DataFrameRowling(df)

+
+

Read dataframe row by row

+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
import pandas as pd
+df = pd.DataFrame({"col1":list(range(100))})
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
res = Parallel(backend="multiprocessing", n_jobs=6)(delayed(get_line)(i) for i in DataFrameRowling(df))
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
res[5]
+
+ +
+
+
+ +
+
+ +
+ + + +
+
col1    5
+Name: 5, dtype: int64
+
+ +
+ +
+
+ +
+ {% endraw %} + +
+ + diff --git a/docs/sidebar.json b/docs/sidebar.json index cff08b1..13258ab 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -10,6 +10,7 @@ "Flatten": "flatten.html", "spacy toolkit": "spacy.html", "FreeMap": "freemap.html", + "Multiprocessing Helper": "multiprocess.html", "Controllable loop process": "loop.html", "ETL Helper": "etl.html", "Get static file": "static_file.html", @@ -20,7 +21,7 @@ "Dataframe filter": "df_filter.html", "Hard drive files": "files.html", "Wild Tree and loss": "wild_tree_and_loss.html", - "Images": "widgets.html", + "Image Widgets": "image_widgets.html", "Categorical Transformation for DL": "category.html", "Cosine ": "cosine_search.html", "Lightning Callbacks": "thunder_callbacks.html", diff --git a/forgebox/__init__.py b/forgebox/__init__.py index b88a962..8f584e6 100644 --- a/forgebox/__init__.py +++ b/forgebox/__init__.py @@ -1 +1 @@ -__version__ = "0.4.18.5" +__version__ = "0.4.19" diff --git a/forgebox/_nbdev.py b/forgebox/_nbdev.py index f88215b..6d0c233 100644 --- a/forgebox/_nbdev.py +++ b/forgebox/_nbdev.py @@ -17,6 +17,9 @@ "Async": "03_async.ipynb", "PandasDisplay": "03_df.ipynb", "DOM": "04_html.ipynb", + "image_to_base64": "04_html.ipynb", + "data_url": "04_html.ipynb", + "img_dom": "04_html.ipynb", "deeper": "04_html.ipynb", "list_group": "04_html.ipynb", "col_sm": "04_html.ipynb", @@ -29,6 +32,11 @@ "make_hboxes": "05_inter_widgets.ipynb", "SingleButton": "05_inter_widgets.ipynb", "Labeler": "05_inter_widgets.ipynb", + "EditableList": "05_inter_widgets.ipynb", + "EditableDict": "05_inter_widgets.ipynb", + "total_width": "05_inter_widgets.ipynb", + "LivingStep": "05_inter_widgets.ipynb", + "StepByStep": "05_inter_widgets.ipynb", "Flatten": "06_flatten.ipynb", "l2norm": "06_spacy.ipynb", "normal": "06_spacy.ipynb", @@ -92,12 +100,10 @@ "WildTree": "40_wild_tree_and_loss.ipynb", "calc_weight": "40_wild_tree_and_loss.ipynb", "loss_package": "40_wild_tree_and_loss.ipynb", - "image_to_base64": "51_widgets.ipynb", - "data_url": "51_widgets.ipynb", - "image_dom": "51_widgets.ipynb", - "with_title_dom": "51_widgets.ipynb", - "view_images": "51_widgets.ipynb", - "Subplots": "51_widgets.ipynb", + "image_dom": "51_image_widgets.ipynb", + "with_title_dom": "51_image_widgets.ipynb", + "view_images": "51_image_widgets.ipynb", + "Subplots": "51_image_widgets.ipynb", "C2I": "53_category.ipynb", "Category": "53_category.ipynb", "TreeCategory": "53_category.ipynb", diff --git a/forgebox/html.py b/forgebox/html.py index 352d3fe..d7f2755 100644 --- a/forgebox/html.py +++ b/forgebox/html.py @@ -1,6 +1,7 @@ # AUTOGENERATED! DO NOT EDIT! File to edit: nbs/04_html.ipynb (unless otherwise specified). -__all__ = ['DOM', 'deeper', 'list_group', 'col_sm', 'list_group_kv', 'JS', 'JS_file'] +__all__ = ['DOM', 'image_to_base64', 'data_url', 'img_dom', 'deeper', 'list_group', 'col_sm', 'list_group_kv', 'JS', + 'JS_file'] # Cell from IPython.display import HTML @@ -61,31 +62,71 @@ def display(self): display(HTML(self.text)) # Cell +from io import BytesIO +import base64 +from PIL.Image import Image as ImageClass + +def image_to_base64( + img: ImageClass +) -> str: + """ + Transform PIL Image to base64 for API + Return: + - base64 encoded image bytes + """ + img = img.convert('RGB') + output_buffer = BytesIO() + img.save(output_buffer, format='JPEG') + byte_data = output_buffer.getvalue() + base64_str = base64.b64encode(byte_data) + return base64_str.decode() + +def data_url( + img: ImageClass +) -> str: + """ + Return: + - data url string, + can be used as the src value of + """ + return f"data:image/jpg;base64,{image_to_base64(img)}" + +def img_dom(img: ImageClass): + return DOM("","img",{"src":data_url(img)}) + +# Cell + def deeper(x): if type(x) in [list, set, tuple]: return list_group(x) if type(x) == dict: return list_group_kv(x) + if type(x) in [int, str, float, bool]: + return x + if type(x) in ImageClass.__subclasses__(): + return img_dom(x) return x + def list_group(iterable): - ul = DOM("","ul",{"class":"list-group"}) + ul = DOM("", "ul", {"class": "list-group"}) for i in iterable: - li = DOM(deeper(i),"li",{"class":"list-group-item"}) + li = DOM(deeper(i), "li", {"class": "list-group-item"}) ul.append(li) return ul # Cell import math -def col_sm(iterable,portions = None,): + + +def col_sm(iterable, portions=None,): if portions == None: - portions = [math.floor(12/len(iterable)),]* len(iterable) - row = DOM("","div",{"class":"row"}) - for i,p in zip(iterable,portions): - row.append(DOM(i,"div",{"class":f"col-sm-{p}"})) + portions = [math.floor(12/len(iterable)), ] * len(iterable) + row = DOM("", "div", {"class": "row"}) + for i, p in zip(iterable, portions): + row.append(DOM(i, "div", {"class": f"col-sm-{p}"})) return row - # Cell def list_group_kv(data): result = [] diff --git a/forgebox/images/widgets.py b/forgebox/images/widgets.py index cee0b79..fd611a0 100644 --- a/forgebox/images/widgets.py +++ b/forgebox/images/widgets.py @@ -1,6 +1,6 @@ -# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/51_widgets.ipynb (unless otherwise specified). +# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/51_image_widgets.ipynb (unless otherwise specified). -__all__ = ['image_to_base64', 'data_url', 'image_dom', 'with_title_dom', 'view_images', 'Subplots'] +__all__ = ['image_dom', 'with_title_dom', 'view_images', 'Subplots'] # Cell import base64 @@ -8,32 +8,8 @@ from PIL import Image from typing import List, Callable import math +from ..html import DOM, image_to_base64, data_url -# Cell -def image_to_base64( - img: Image -) -> str: - """ - Transform PIL Image to base64 for API - Return: - - base64 encoded image bytes - """ - img = img.convert('RGB') - output_buffer = BytesIO() - img.save(output_buffer, format='JPEG') - byte_data = output_buffer.getvalue() - base64_str = base64.b64encode(byte_data) - return base64_str.decode() - -def data_url( - img: Image -) -> str: - """ - Return: - - data url string, - can be used as the src value of - """ - return f"data:image/jpg;base64,{image_to_base64(img)}" # Cell def image_dom( diff --git a/forgebox/widgets.py b/forgebox/widgets.py index 74f0730..7e6d5fd 100644 --- a/forgebox/widgets.py +++ b/forgebox/widgets.py @@ -1,15 +1,21 @@ # AUTOGENERATED! DO NOT EDIT! File to edit: nbs/05_inter_widgets.ipynb (unless otherwise specified). -__all__ = ['display_df', 'search_box', 'paginate', 'make_hboxes', 'SingleButton', 'Labeler'] +__all__ = ['display_df', 'search_box', 'paginate', 'make_hboxes', 'SingleButton', 'Labeler', 'EditableList', + 'EditableDict', 'total_width', 'LivingStep', 'StepByStep'] # Cell import pandas as pd import numpy as np from .df import PandasDisplay -from typing import Callable, List, Tuple, Set, Dict -from ipywidgets import IntSlider, FloatSlider, Text, Textarea, Layout,\ - Output, HBox, VBox, Button, Select, SelectMultiple, Dropdown,\ - IntProgress, HTML +from .html import list_group, list_group_kv +from typing import Callable, List, Tuple, Set, Dict, Any +from ipywidgets import ( + IntSlider, FloatSlider, Text, Textarea, Layout, + Output, HBox, VBox, Button, + Select, SelectMultiple, + Dropdown, Checkbox, + IntProgress, HTML, interact, interact_manual +) # Cell def display_df(df):display(df) @@ -309,4 +315,321 @@ def show_func(self, idx, row) -> None: self.get_progress_bar(idx), HTML(self.show_callback(idx, row)), self.button_box(idx, row[self.options_col]) - ])) \ No newline at end of file + ])) + +# Cell +total_width = Layout(width="100%") + + +class EditableList(VBox): + """ + Interactive list + You can add item to the list + Each added item has a remove button to remove such item + """ + + def __init__(self, data_list: List[Any] = [], pretty_json: bool = True): + super().__init__([], layout=total_width) + self.pretty_json = pretty_json + for data in data_list: + self+data + + def create_line(self, data): + children = list(self.children) + children.append(self.new_line(data)) + self.children = children + + def data_to_dom(self, data): + if self.pretty_json: + pretty = list_group_kv(data) if hasattr( + data, "keys") else list_group(data) + return HTML(str(pretty), layout=total_width) + else: + return HTML(json.dumps(data)) + + def new_line(self, data) -> HBox: + del_btn = Button(description="Remove", icon="trash") + del_btn.button_style = 'danger' + hbox = HBox([del_btn, self.data_to_dom(data)], + layout=total_width, box_style='info') + hbox.data = data + + def remove_hbox(): + children = list(self.children) + for i, c in enumerate(children): + if id(c) == id(hbox): + children.remove(c) + self.children = children + del_btn.click = remove_hbox + return hbox + + def __add__(self, data): + self.create_line(data) + return self + + def get_data(self) -> List[Any]: + """ + Return the data of this list + """ + return list(x.data for x in self.children) + + +class EditableDict(VBox): + """ + Interactive dictionary + You can add item to the dictionary + Each added item has a remove button to remove such item + """ + def __init__(self, data_dict: Dict[str, Any] = dict(), pretty_json: bool = True): + super().__init__([], layout=total_width) + self.pretty_json = pretty_json + self+data_dict + + def on_update(self, func): + """ + A decorator to set a function + Every time the dict changed + Will execute this function + the default arg is the dictionary data + """ + self.update_func = func + return func + + def run_update(self): + if hasattr(self, "update_func"): + self.update_func(self.get_data()) + + def create_line(self, key: str, data: Any): + children_map = dict((child.key, child) for child in self.children) + children_map[key] = self.new_line(key, data) + self.children = list(children_map.values()) + self.run_update() + + def data_to_dom(self, data): + if self.pretty_json: + pretty = list_group_kv(data) if hasattr( + data, "keys") else list_group(data) + return HTML(str(pretty), layout=total_width) + else: + return HTML(json.dumps(data)) + + def new_line(self, key: str, data: Any) -> HBox: + del_btn = Button(description="Remove", icon="trash") + del_btn.button_style = 'danger' + key_info = HTML(f"

{key}

") + hbox = HBox([VBox([key_info, del_btn]), self.data_to_dom(data)], + layout=total_width, box_style='') + hbox.data = data + hbox.key = key + + def remove_hbox(): + children = list(self.children) + for c in children: + if id(c) == id(hbox): + children.remove(c) + self.children = children + self.run_update() + del_btn.click = remove_hbox + return hbox + + def __setitem__(self, k, v): + self.create_line(k, v) + + def __add__(self, kv): + for k,v in kv.items(): + self.create_line(k, v) + return self + + def get_data(self) -> Dict[str, Any]: + """ + Return the data of this dict + """ + return dict((x.key,x.data) for x in self.children) + +# Cell +class LivingStep: + """ + A step interactive for StepByStep + """ + + def __init__( + self, func: Callable, + top_block: HTML = None + ): + self.output = Output() + self.func = func + self.top_block = top_block + + def __call__(self, **kwargs): + with self.output: + if self.top_block is not None: + display(self.top_block) + return self.func(**kwargs) + + def new_top_block(self, top_block): + self.top_block = top_block + + +class StepByStep: + """ + A tool to manage progress step by step + """ + + def __init__( + self, + funcs: Dict[str, Callable], + top_board: HTML = None, + kwargs: Dict[str, Any] = dict() + ): + self.step_keys: List[str] = list(funcs.keys()) + self.steps: Dict[str, LivingStep] = dict( + (k, LivingStep(f)) for k, f in funcs.items()) + self.furthest: int = 0 + self.current: int = -1 + self.kwargs: Dict[str, Any] = kwargs + self.execute_cache: Dict[str, bool] = dict() + self.top_board: HTML = top_board + self.page_output: Output = Output() + self.footer: Output = Output() + self.create_widget() + + def rerun(self,**kwargs): + """ + Rerun the current step function + """ + # find the step + step: LivingStep = self.steps[self.step_keys[self.current]] + # clear old output + step.output.clear_output() + self.kwargs.update(kwargs) + step(progress=self, **self.kwargs) + + def create_control_bar(self,): + self.bar_hbox = list() + self.next_btn: Button = Button( + description="Next", icon='check', button_style='info') + self.rerun_btn = Button(description="Rerun Step", + icon='play', button_style='success') + self.title = HTML(f"

Step By Step

") + self.next_btn.click = self.next_step + self.rerun_btn.click = self.rerun + self.bar_hbox.append(self.title) + self.bar_hbox.append(self.next_btn) + self.bar_hbox.append(self.rerun_btn) + return HBox(self.bar_hbox) + + def create_widget(self) -> None: + self.vbox_list = [] + if self.top_board is not None: + self.vbox_list.append(self.top_board) + + # create buttons for progress axis + self.progress_btns = dict( + (k, Button( + description=f"{i+1}:{k}", + icon="cube", + button_style="danger" + if i <= self.furthest else "")) + for i, (k, v) in enumerate(self.steps.items()) + ) + # assign action to first button + first_btn: Button = list(self.progress_btns.values())[0] + first_btn.click: Callable = self.to_page_action(0) + self.progress_bar = HBox(list(self.progress_btns.values())) + + # assemble the entire widget + self.vbox_list.append(self.progress_bar) + self.vbox_list.append(self.create_control_bar()) + self.vbox_list.append(self.page_output) + self.widget = VBox(self.vbox_list) + + def to_page_action( + self, page_id: int + ) -> Callable: + """ + generate the button click function + """ + def to_page_func(): + return self[page_id] + return to_page_func + + def update_furthest(self): + """ + Update the "furthest mark" + Also enact the next progress button + """ + if self.furthest < self.current: + if self.current < len(self): + # update even button + btn = self.progress_btns[self.step_keys[self.current]] + btn.click = self.to_page_action( + self.current) + btn.button_style = 'danger' + self.furthest = self.current + + def __repr__(self): + keys = " => ".join(self.step_keys) + return f"Progress Axis: [{keys}]" + + def __getitem__(self, page_id): + """ + Display a single page + """ + if (page_id < 0) or (page_id >= len(self)): + return + self.current: int = page_id + key: str = self.step_keys[page_id] + step: LivingStep = self.steps[key] + self.title.value: str = f"

Step {page_id+1}: {key}

" + self.page_output.clear_output() + + with self.page_output: + display(step.output) + if key not in self.execute_cache: + rt = step(progress=self, **self.kwargs) + if hasattr(rt,"keys"): + self.kwargs(rt) + self.execute_cache[key] = True + + def next_step(self, **kwargs): + self.current += 1 + if self.current >= len(self): + self.current = 0 + self.update_furthest() + return self[self.current] + + def __len__(self): + return len(self.step_keys) + + def __call__(self, **kwargs): + """ + Start the entire progress widget + """ + display(self.widget) + display(self.footer) + self.kwargs.update(kwargs) + self.next_step(**self.kwargs) + + def show_in(self, step_name: str) -> Callable: + """ + A decorator that will make the function + to show under a specific step window + """ + step = self.steps[step_name] + + def decorator(func: Callable) -> Callable: + def wrapper(*args, **kwargs): + with step.output: + return func(*args, **kwargs) + return wrapper + return decorator + + def show_footer(self, func: Callable): + """ + A decorator, where functions excuted + within this, will be showon under footer + """ + def wrapper(*args, **kwargs): + with self.footer: + return func(*args, **kwargs) + return wrapper \ No newline at end of file diff --git a/nbs/04_html.ipynb b/nbs/04_html.ipynb index 435afd8..29a9b79 100644 --- a/nbs/04_html.ipynb +++ b/nbs/04_html.ipynb @@ -276,47 +276,94 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 64, + "metadata": {}, + "outputs": [], + "source": [ + "# export\n", + "from io import BytesIO\n", + "import base64\n", + "from PIL.Image import Image as ImageClass\n", + "\n", + "def image_to_base64(\n", + " img: ImageClass\n", + ") -> str:\n", + " \"\"\"\n", + " Transform PIL Image to base64 for API\n", + " Return:\n", + " - base64 encoded image bytes\n", + " \"\"\"\n", + " img = img.convert('RGB')\n", + " output_buffer = BytesIO()\n", + " img.save(output_buffer, format='JPEG')\n", + " byte_data = output_buffer.getvalue()\n", + " base64_str = base64.b64encode(byte_data)\n", + " return base64_str.decode()\n", + "\n", + "def data_url(\n", + " img: ImageClass\n", + ") -> str:\n", + " \"\"\"\n", + " Return:\n", + " - data url string,\n", + " can be used as the src value of \n", + " \"\"\"\n", + " return f\"data:image/jpg;base64,{image_to_base64(img)}\"\n", + "\n", + "def img_dom(img: ImageClass):\n", + " return DOM(\"\",\"img\",{\"src\":data_url(img)})" + ] + }, + { + "cell_type": "code", + "execution_count": 67, "metadata": {}, "outputs": [], "source": [ - "# export \n", + "# export\n", + "\n", "def deeper(x):\n", " if type(x) in [list, set, tuple]:\n", " return list_group(x)\n", " if type(x) == dict:\n", " return list_group_kv(x)\n", + " if type(x) in [int, str, float, bool]:\n", + " return x\n", + " if type(x) in ImageClass.__subclasses__():\n", + " return img_dom(x)\n", " return x\n", "\n", + "\n", "def list_group(iterable):\n", - " ul = DOM(\"\",\"ul\",{\"class\":\"list-group\"})\n", + " ul = DOM(\"\", \"ul\", {\"class\": \"list-group\"})\n", " for i in iterable:\n", - " li = DOM(deeper(i),\"li\",{\"class\":\"list-group-item\"})\n", + " li = DOM(deeper(i), \"li\", {\"class\": \"list-group-item\"})\n", " ul.append(li)\n", " return ul" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 68, "metadata": {}, "outputs": [], "source": [ "# export\n", "import math\n", - "def col_sm(iterable,portions = None,):\n", + "\n", + "\n", + "def col_sm(iterable, portions=None,):\n", " if portions == None:\n", - " portions = [math.floor(12/len(iterable)),]* len(iterable)\n", - " row = DOM(\"\",\"div\",{\"class\":\"row\"})\n", - " for i,p in zip(iterable,portions):\n", - " row.append(DOM(i,\"div\",{\"class\":f\"col-sm-{p}\"}))\n", - " return row\n", - " " + " portions = [math.floor(12/len(iterable)), ] * len(iterable)\n", + " row = DOM(\"\", \"div\", {\"class\": \"row\"})\n", + " for i, p in zip(iterable, portions):\n", + " row.append(DOM(i, \"div\", {\"class\": f\"col-sm-{p}\"}))\n", + " return row" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 69, "metadata": {}, "outputs": [ { @@ -338,7 +385,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 70, "metadata": {}, "outputs": [ { @@ -360,7 +407,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 71, "metadata": {}, "outputs": [], "source": [ diff --git a/nbs/05_inter_widgets.ipynb b/nbs/05_inter_widgets.ipynb index 4372ab3..47aca95 100644 --- a/nbs/05_inter_widgets.ipynb +++ b/nbs/05_inter_widgets.ipynb @@ -19,7 +19,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -27,10 +27,15 @@ "import pandas as pd\n", "import numpy as np\n", "from forgebox.df import PandasDisplay\n", - "from typing import Callable, List, Tuple, Set, Dict\n", - "from ipywidgets import IntSlider, FloatSlider, Text, Textarea, Layout,\\\n", - " Output, HBox, VBox, Button, Select, SelectMultiple, Dropdown,\\\n", - " IntProgress, HTML" + "from forgebox.html import list_group, list_group_kv\n", + "from typing import Callable, List, Tuple, Set, Dict, Any\n", + "from ipywidgets import (\n", + " IntSlider, FloatSlider, Text, Textarea, Layout,\n", + " Output, HBox, VBox, Button,\n", + " Select, SelectMultiple,\n", + " Dropdown, Checkbox,\n", + " IntProgress, HTML, interact, interact_manual\n", + ")" ] }, { @@ -921,6 +926,422 @@ "source": [ "lbl.df.head()" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Editable list and editable dict" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# export\n", + "total_width = Layout(width=\"100%\")\n", + "\n", + "\n", + "class EditableList(VBox):\n", + " \"\"\"\n", + " Interactive list\n", + " You can add item to the list\n", + " Each added item has a remove button to remove such item\n", + " \"\"\"\n", + "\n", + " def __init__(self, data_list: List[Any] = [], pretty_json: bool = True):\n", + " super().__init__([], layout=total_width)\n", + " self.pretty_json = pretty_json\n", + " for data in data_list:\n", + " self+data\n", + "\n", + " def create_line(self, data):\n", + " children = list(self.children)\n", + " children.append(self.new_line(data))\n", + " self.children = children\n", + "\n", + " def data_to_dom(self, data):\n", + " if self.pretty_json:\n", + " pretty = list_group_kv(data) if hasattr(\n", + " data, \"keys\") else list_group(data)\n", + " return HTML(str(pretty), layout=total_width)\n", + " else:\n", + " return HTML(json.dumps(data))\n", + "\n", + " def new_line(self, data) -> HBox:\n", + " del_btn = Button(description=\"Remove\", icon=\"trash\")\n", + " del_btn.button_style = 'danger'\n", + " hbox = HBox([del_btn, self.data_to_dom(data)],\n", + " layout=total_width, box_style='info')\n", + " hbox.data = data\n", + "\n", + " def remove_hbox():\n", + " children = list(self.children)\n", + " for i, c in enumerate(children):\n", + " if id(c) == id(hbox):\n", + " children.remove(c)\n", + " self.children = children\n", + " del_btn.click = remove_hbox\n", + " return hbox\n", + "\n", + " def __add__(self, data):\n", + " self.create_line(data)\n", + " return self\n", + "\n", + " def get_data(self) -> List[Any]:\n", + " \"\"\"\n", + " Return the data of this list\n", + " \"\"\"\n", + " return list(x.data for x in self.children)\n", + "\n", + "\n", + "class EditableDict(VBox):\n", + " \"\"\"\n", + " Interactive dictionary\n", + " You can add item to the dictionary\n", + " Each added item has a remove button to remove such item\n", + " \"\"\"\n", + " def __init__(self, data_dict: Dict[str, Any] = dict(), pretty_json: bool = True):\n", + " super().__init__([], layout=total_width)\n", + " self.pretty_json = pretty_json\n", + " self+data_dict\n", + " \n", + " def on_update(self, func):\n", + " \"\"\"\n", + " A decorator to set a function\n", + " Every time the dict changed\n", + " Will execute this function\n", + " the default arg is the dictionary data\n", + " \"\"\"\n", + " self.update_func = func\n", + " return func\n", + " \n", + " def run_update(self):\n", + " if hasattr(self, \"update_func\"):\n", + " self.update_func(self.get_data())\n", + " \n", + " def create_line(self, key: str, data: Any):\n", + " children_map = dict((child.key, child) for child in self.children)\n", + " children_map[key] = self.new_line(key, data)\n", + " self.children = list(children_map.values())\n", + " self.run_update()\n", + " \n", + " def data_to_dom(self, data):\n", + " if self.pretty_json:\n", + " pretty = list_group_kv(data) if hasattr(\n", + " data, \"keys\") else list_group(data)\n", + " return HTML(str(pretty), layout=total_width)\n", + " else:\n", + " return HTML(json.dumps(data))\n", + "\n", + " def new_line(self, key: str, data: Any) -> HBox:\n", + " del_btn = Button(description=\"Remove\", icon=\"trash\")\n", + " del_btn.button_style = 'danger'\n", + " key_info = HTML(f\"

{key}

\")\n", + " hbox = HBox([VBox([key_info, del_btn]), self.data_to_dom(data)],\n", + " layout=total_width, box_style='')\n", + " hbox.data = data\n", + " hbox.key = key\n", + "\n", + " def remove_hbox():\n", + " children = list(self.children)\n", + " for c in children:\n", + " if id(c) == id(hbox):\n", + " children.remove(c)\n", + " self.children = children\n", + " self.run_update()\n", + " del_btn.click = remove_hbox\n", + " return hbox\n", + " \n", + " def __setitem__(self, k, v):\n", + " self.create_line(k, v)\n", + " \n", + " def __add__(self, kv):\n", + " for k,v in kv.items():\n", + " self.create_line(k, v)\n", + " return self\n", + "\n", + " def get_data(self) -> Dict[str, Any]:\n", + " \"\"\"\n", + " Return the data of this dict\n", + " \"\"\"\n", + " return dict((x.key,x.data) for x in self.children)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step by step" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "# export\n", + "class LivingStep:\n", + " \"\"\"\n", + " A step interactive for StepByStep\n", + " \"\"\"\n", + "\n", + " def __init__(\n", + " self, func: Callable,\n", + " top_block: HTML = None\n", + " ):\n", + " self.output = Output()\n", + " self.func = func\n", + " self.top_block = top_block\n", + "\n", + " def __call__(self, **kwargs):\n", + " with self.output:\n", + " if self.top_block is not None:\n", + " display(self.top_block)\n", + " return self.func(**kwargs)\n", + "\n", + " def new_top_block(self, top_block):\n", + " self.top_block = top_block\n", + "\n", + "\n", + "class StepByStep:\n", + " \"\"\"\n", + " A tool to manage progress step by step\n", + " \"\"\"\n", + "\n", + " def __init__(\n", + " self,\n", + " funcs: Dict[str, Callable],\n", + " top_board: HTML = None,\n", + " kwargs: Dict[str, Any] = dict()\n", + " ):\n", + " self.step_keys: List[str] = list(funcs.keys())\n", + " self.steps: Dict[str, LivingStep] = dict(\n", + " (k, LivingStep(f)) for k, f in funcs.items())\n", + " self.furthest: int = 0\n", + " self.current: int = -1\n", + " self.kwargs: Dict[str, Any] = kwargs\n", + " self.execute_cache: Dict[str, bool] = dict()\n", + " self.top_board: HTML = top_board\n", + " self.page_output: Output = Output()\n", + " self.footer: Output = Output()\n", + " self.create_widget()\n", + "\n", + " def rerun(self,**kwargs):\n", + " \"\"\"\n", + " Rerun the current step function\n", + " \"\"\"\n", + " # find the step\n", + " step: LivingStep = self.steps[self.step_keys[self.current]]\n", + " # clear old output\n", + " step.output.clear_output()\n", + " self.kwargs.update(kwargs)\n", + " step(progress=self, **self.kwargs)\n", + "\n", + " def create_control_bar(self,):\n", + " self.bar_hbox = list()\n", + " self.next_btn: Button = Button(\n", + " description=\"Next\", icon='check', button_style='info')\n", + " self.rerun_btn = Button(description=\"Rerun Step\",\n", + " icon='play', button_style='success')\n", + " self.title = HTML(f\"

Step By Step

\")\n", + " self.next_btn.click = self.next_step\n", + " self.rerun_btn.click = self.rerun\n", + " self.bar_hbox.append(self.title)\n", + " self.bar_hbox.append(self.next_btn)\n", + " self.bar_hbox.append(self.rerun_btn)\n", + " return HBox(self.bar_hbox)\n", + "\n", + " def create_widget(self) -> None:\n", + " self.vbox_list = []\n", + " if self.top_board is not None:\n", + " self.vbox_list.append(self.top_board)\n", + " \n", + " # create buttons for progress axis\n", + " self.progress_btns = dict(\n", + " (k, Button(\n", + " description=f\"{i+1}:{k}\",\n", + " icon=\"cube\",\n", + " button_style=\"danger\"\n", + " if i <= self.furthest else \"\"))\n", + " for i, (k, v) in enumerate(self.steps.items())\n", + " )\n", + " # assign action to first button\n", + " first_btn: Button = list(self.progress_btns.values())[0]\n", + " first_btn.click: Callable = self.to_page_action(0)\n", + " self.progress_bar = HBox(list(self.progress_btns.values()))\n", + " \n", + " # assemble the entire widget\n", + " self.vbox_list.append(self.progress_bar)\n", + " self.vbox_list.append(self.create_control_bar())\n", + " self.vbox_list.append(self.page_output)\n", + " self.widget = VBox(self.vbox_list)\n", + "\n", + " def to_page_action(\n", + " self, page_id: int\n", + " ) -> Callable:\n", + " \"\"\"\n", + " generate the button click function\n", + " \"\"\"\n", + " def to_page_func():\n", + " return self[page_id]\n", + " return to_page_func\n", + "\n", + " def update_furthest(self):\n", + " \"\"\"\n", + " Update the \"furthest mark\"\n", + " Also enact the next progress button\n", + " \"\"\"\n", + " if self.furthest < self.current:\n", + " if self.current < len(self):\n", + " # update even button\n", + " btn = self.progress_btns[self.step_keys[self.current]]\n", + " btn.click = self.to_page_action(\n", + " self.current)\n", + " btn.button_style = 'danger'\n", + " self.furthest = self.current\n", + " \n", + " def __repr__(self):\n", + " keys = \" => \".join(self.step_keys)\n", + " return f\"Progress Axis: [{keys}]\"\n", + "\n", + " def __getitem__(self, page_id):\n", + " \"\"\"\n", + " Display a single page\n", + " \"\"\"\n", + " if (page_id < 0) or (page_id >= len(self)):\n", + " return\n", + " self.current: int = page_id\n", + " key: str = self.step_keys[page_id]\n", + " step: LivingStep = self.steps[key]\n", + " self.title.value: str = f\"

Step {page_id+1}: {key}

\"\n", + " self.page_output.clear_output()\n", + "\n", + " with self.page_output:\n", + " display(step.output)\n", + " if key not in self.execute_cache:\n", + " rt = step(progress=self, **self.kwargs)\n", + " if hasattr(rt,\"keys\"):\n", + " self.kwargs(rt)\n", + " self.execute_cache[key] = True\n", + "\n", + " def next_step(self, **kwargs):\n", + " self.current += 1\n", + " if self.current >= len(self):\n", + " self.current = 0\n", + " self.update_furthest()\n", + " return self[self.current]\n", + "\n", + " def __len__(self):\n", + " return len(self.step_keys)\n", + "\n", + " def __call__(self, **kwargs):\n", + " \"\"\"\n", + " Start the entire progress widget\n", + " \"\"\"\n", + " display(self.widget)\n", + " display(self.footer)\n", + " self.kwargs.update(kwargs)\n", + " self.next_step(**self.kwargs)\n", + "\n", + " def show_in(self, step_name: str) -> Callable:\n", + " \"\"\"\n", + " A decorator that will make the function\n", + " to show under a specific step window\n", + " \"\"\"\n", + " step = self.steps[step_name]\n", + "\n", + " def decorator(func: Callable) -> Callable:\n", + " def wrapper(*args, **kwargs):\n", + " with step.output:\n", + " return func(*args, **kwargs)\n", + " return wrapper\n", + " return decorator\n", + "\n", + " def show_footer(self, func: Callable):\n", + " \"\"\"\n", + " A decorator, where functions excuted\n", + " within this, will be showon under footer\n", + " \"\"\"\n", + " def wrapper(*args, **kwargs):\n", + " with self.footer:\n", + " return func(*args, **kwargs)\n", + " return wrapper" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "def test_step(**kwargs):\n", + " @interact\n", + " def show_step_(how_much=[1,2,4,8,16], charactor = ['šŸŗ', 'šŸŗ', 'šŸ¶','šŸ·']):\n", + " return pd.DataFrame({\"stuff\":[charactor*how_much,]*how_much})" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "sbs = StepByStep(\n", + " {\"step1\":test_step,\"some_step\":test_step, \"another_step\":test_step},\n", + " HTML(\"\"\"\n", + "

Detail Steps

\n", + " \"\"\")\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "44d422d4246a456fa273f7bd7a683523", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(HTML(value='\\n

Detail Steps

\\n '), HBox(children=(Button(button_style='danger', dā€¦" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d5e759edcf114dabb2772dfb2ea7bbc1", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sbs()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/nbs/51_widgets.ipynb b/nbs/51_image_widgets.ipynb similarity index 98% rename from nbs/51_widgets.ipynb rename to nbs/51_image_widgets.ipynb index e70d320..7a61fbc 100644 --- a/nbs/51_widgets.ipynb +++ b/nbs/51_image_widgets.ipynb @@ -4,8 +4,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Images\n", - "> Image widgets for jupyter noteook" + "# Image Widgets\n", + "> Extra widgets for jupyter noteook" ] }, { @@ -35,48 +35,15 @@ "from io import BytesIO\n", "from PIL import Image\n", "from typing import List, Callable\n", - "import math" + "import math\n", + "from forgebox.html import DOM, image_to_base64, data_url\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Data url for img tag\n", - "### Use data url in ``````" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "# export\n", - "def image_to_base64(\n", - " img: Image\n", - ") -> str:\n", - " \"\"\"\n", - " Transform PIL Image to base64 for API\n", - " Return:\n", - " - base64 encoded image bytes\n", - " \"\"\"\n", - " img = img.convert('RGB')\n", - " output_buffer = BytesIO()\n", - " img.save(output_buffer, format='JPEG')\n", - " byte_data = output_buffer.getvalue()\n", - " base64_str = base64.b64encode(byte_data)\n", - " return base64_str.decode()\n", - "\n", - "def data_url(\n", - " img: Image\n", - ") -> str:\n", - " \"\"\"\n", - " Return:\n", - " - data url string,\n", - " can be used as the src value of \n", - " \"\"\"\n", - " return f\"data:image/jpg;base64,{image_to_base64(img)}\"" + "## Data url for img tag" ] }, { @@ -374,13 +341,6 @@ " img, img, img, img, img, img,\n", " num_per_row=6)()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/settings.ini b/settings.ini index 02295be..1b4d4fb 100644 --- a/settings.ini +++ b/settings.ini @@ -7,7 +7,7 @@ author = xiaochen(ray) zhang author_email = b2ray2c@gmail.com copyright = xiaochen(ray) zhang branch = master -version = 0.4.18.5 +version = 0.4.19 min_python = 3.6 audience = Developers language = English