-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(bindgen): Add python-web-demo interface
- Loading branch information
Showing
40 changed files
with
1,878 additions
and
213 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# itkwasm-compress-stringify | ||
|
||
[![PyPI version](https://badge.fury.io/py/itkwasm-compress-stringify.svg)](https://badge.fury.io/py/itkwasm-compress-stringify) | ||
|
||
> API Demo: Zstandard compression and decompression and base64 encoding and decoding in WebAssembly. | ||
This is a simple web browser app built with Python, HTML, and CSS that demonstrates the [itkwasm-compress-stringify](https://badge.fury.io/py/itkwasm-compress-stringify) Python package. | ||
|
||
## Development | ||
|
||
The app uses the Python package published on pypi.org and the corresponding JavaScript package published npmjs.com. | ||
|
||
The web app can be developed locally with any http server that serves static files. For example: | ||
|
||
```sh | ||
python -m http.server | ||
``` |
112 changes: 112 additions & 0 deletions
112
packages/compress-stringify/python-web-demo/compress_stringify.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
# Generated file. To retain edits, remove this comment. | ||
|
||
from dataclasses import dataclass | ||
from typing import Any, Dict | ||
|
||
import numpy as np | ||
|
||
import js | ||
from pyodide.ffi.wrappers import add_event_listener | ||
import pyodide | ||
|
||
from itkwasm_compress_stringify import compress_stringify_async | ||
from compress_stringify_load_sample_inputs import load_sample_inputs | ||
|
||
@dataclass | ||
class CompressStringifyModel: | ||
inputs: Dict['str', Any] | ||
options: Dict['str', Any] | ||
outputs: Dict['str', Any] | ||
|
||
class CompressStringifyController: | ||
|
||
def __init__(self, load_sample_inputs): | ||
self.model = CompressStringifyModel({}, {}, {}) | ||
|
||
self.load_sample_inputs = load_sample_inputs | ||
if load_sample_inputs is not None: | ||
load_sample_inputs_button = js.document.querySelector("#compress_stringify-inputs [name=load-sample-inputs]") | ||
load_sample_inputs_button.setAttribute('style', 'display: block-inline;') | ||
add_event_listener(load_sample_inputs_button, 'click', self.on_load_sample_inputs_click) | ||
|
||
# Inputs | ||
input_element = js.document.querySelector('#compress_stringify-inputs input[name=input-file]') | ||
add_event_listener(input_element, 'change', self.on_input_change) | ||
|
||
# Options | ||
stringify_element = js.document.querySelector('#compress_stringify-inputs sl-checkbox[name=stringify]') | ||
self.stringify_element = stringify_element | ||
add_event_listener(stringify_element, 'sl-change', self.on_stringify_change) | ||
|
||
compression_level_element = js.document.querySelector('#compress_stringify-inputs sl-input[name=compression-level]') | ||
self.compression_level_element = compression_level_element | ||
add_event_listener(compression_level_element, 'sl-change', self.on_compression_level_change) | ||
|
||
data_url_prefix_element = js.document.querySelector('#compress_stringify-inputs sl-input[name=data-url-prefix]') | ||
self.data_url_prefix_element = data_url_prefix_element | ||
add_event_listener(data_url_prefix_element, 'sl-change', self.on_data_url_prefix_change) | ||
|
||
# Outputs | ||
output_download_element = js.document.querySelector('#compress_stringify-outputs sl-button[name=output-download]') | ||
self.output_download_element = output_download_element | ||
add_event_listener(output_download_element, 'click', self.on_output_click) | ||
|
||
# Run | ||
run_button = js.document.querySelector('#compress_stringify-inputs sl-button[name="run"]') | ||
self.run_button = run_button | ||
add_event_listener(run_button, 'click', self.on_run) | ||
|
||
def on_load_sample_inputs_click(self, event): | ||
self.model = self.load_sample_inputs(self.model) | ||
|
||
async def on_input_change(self, event): | ||
files = event.target.files | ||
array_buffer = await files.item(0).arrayBuffer() | ||
input_bytes = array_buffer.to_bytes() | ||
self.model.inputs['input'] = input_bytes | ||
input_element = js.document.querySelector("#compress_stringify-inputs sl-input[name=input]") | ||
input_element.value = str(np.frombuffer(input_bytes[:50], dtype=np.uint8)) + ' ...' | ||
|
||
def on_stringify_change(self, event): | ||
self.model.options['stringify'] = self.stringify_element.checked | ||
|
||
def on_compression_level_change(self, event): | ||
self.model.options['compression_level'] = int(self.compression_level_element.value) | ||
|
||
def on_data_url_prefix_change(self, event): | ||
self.model.options['data_url_prefix'] = self.data_url_prefix_element.value | ||
|
||
def on_output_click(self, event): | ||
if 'output' not in self.model.outputs: | ||
return | ||
output = pyodide.ffi.to_js(self.model.outputs['output']) | ||
js.globalThis.downloadFile(output, 'output.bin') | ||
|
||
async def on_run(self, event): | ||
event.preventDefault() | ||
event.stopPropagation() | ||
|
||
if 'input' not in self.model.inputs: | ||
|
||
js.globalThis.notify("Error while running pipeline", "Missing input 'input'", "danger", "exclamation-octagon") | ||
return | ||
|
||
self.run_button.loading = True | ||
try: | ||
t0 = js.performance.now() | ||
output = await compress_stringify_async(self.model.inputs['input'], **self.model.options) | ||
t1 = js.performance.now() | ||
js.globalThis.notify("compress_stringify successfully completed", f"in {t1 - t0} milliseconds.", "success", "rocket-fill") | ||
self.model.outputs["output"] = output | ||
self.output_download_element.variant = "success" | ||
self.output_download_element.disabled = False | ||
output_element = js.document.querySelector('#compress_stringify-outputs sl-textarea[name=output]') | ||
output_element.value = str(np.frombuffer(output[:200], dtype=np.uint8)) + ' ...' | ||
|
||
except Exception as error: | ||
js.globalThis.notify("Error while running pipeline", str(error), "danger", "exclamation-octagon") | ||
raise error | ||
finally: | ||
self.run_button.loading = False | ||
|
||
compress_stringify_controller = CompressStringifyController(load_sample_inputs) |
26 changes: 26 additions & 0 deletions
26
packages/compress-stringify/python-web-demo/compress_stringify_load_sample_inputs.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import js | ||
|
||
# load_sample_inputs = None | ||
|
||
def load_sample_inputs(model): | ||
sample_input = bytes([222, 173, 190, 239]) | ||
model.inputs['input'] = sample_input | ||
input_element = js.document.querySelector('#compress_stringify-inputs sl-input[name=input]') | ||
input_element.value = str(sample_input) | ||
|
||
stringify = True | ||
model.options['stringify'] = stringify | ||
stringify_element = js.document.querySelector('#compress_stringify-inputs sl-checkbox[name=stringify]') | ||
stringify_element.checked = stringify | ||
|
||
compression_level = 5 | ||
model.options['compression_level'] = compression_level | ||
compression_level_element = js.document.querySelector('#compress_stringify-inputs sl-input[name=compression-level]') | ||
compression_level_element.value = compression_level | ||
|
||
data_url_prefix = 'data:application/iwi+cbor+zstd;base64,' | ||
model.options['data_url_prefix'] = data_url_prefix | ||
data_url_prefix_element = js.document.querySelector('#compress_stringify-inputs sl-input[name=data-url-prefix]') | ||
data_url_prefix_element.value = data_url_prefix | ||
|
||
return model |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<link rel="icon" type="image/svg+xml" href="./logo.svg" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<link rel="stylesheet" href="./style.css" /> | ||
|
||
<link | ||
rel="stylesheet" | ||
media="(prefers-color-scheme:light)" | ||
href="https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/cdn/themes/light.css" | ||
/> | ||
<link | ||
rel="stylesheet" | ||
media="(prefers-color-scheme:dark)" | ||
href="https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/cdn/themes/dark.css" | ||
onload="document.documentElement.classList.add('sl-theme-dark');" | ||
/> | ||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/cdn/shoelace-autoloader.js"></script> | ||
|
||
<script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script> | ||
|
||
<title>itkwasm-compress-stringify<img src="./python-logo.svg" alt="Python logo" class="language-logo"/></title> | ||
</head> | ||
<body> | ||
|
||
<script src="./utilities.js"></script> | ||
|
||
<!-- https://tholman.com/github-corners/ --> | ||
<a href="https://github.com/InsightSoftwareConsortium/itk-wasm" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a> | ||
|
||
<div id="app"> | ||
<a href="https://wasm.itk.org/" target="_blank"> | ||
<img src="./logo.svg" class="logo vanilla" alt="ITK Wasm logo" /> | ||
</a> | ||
|
||
<h2>itkwasm-compress-stringify<img src="./python-logo.svg" alt="Python logo" class="language-logo"/></h2> | ||
<i>Zstandard compression and decompression and base64 encoding and decoding in WebAssembly.</i> | ||
<br /><br /> | ||
|
||
<sl-tab-group> | ||
<sl-tab slot="nav" panel="compress_stringify-panel">compress_stringify</sl-tab> | ||
<sl-tab slot="nav" panel="parse_string_decompress-panel">parse_string_decompress</sl-tab> | ||
|
||
|
||
<sl-tab-panel name="compress_stringify-panel"> | ||
|
||
<small><i>Given a binary, compress and optionally base64 encode.</i></small><br /><br /> | ||
|
||
<div id="compress_stringify-inputs"><form action=""> | ||
<sl-input required name="input" type="text" label="input" help-text="Input binary" disabled></sl-input> | ||
<label for="input-file"><sl-button required variant="primary" outline onclick="this.parentElement.nextElementSibling.click()">Upload</sl-button></label><input type="file" name="input-file" style="display: none"/> | ||
<br /><br /> | ||
<sl-checkbox name="stringify">stringify - <i>Stringify the output</i></sl-checkbox> | ||
<br /><br /> | ||
<sl-input name="compression-level" type="number" value="3" label="compression_level" help-text="Compression level, typically 1-9"></sl-input> | ||
<br /> | ||
<sl-input name="data-url-prefix" type="text" label="data_url_prefix" help-text="dataURL prefix"></sl-input> | ||
<sl-divider></sl-divider> | ||
<br /><sl-button name="load-sample-inputs" variant="default" style="display: none;">Load sample inputs</sl-button> | ||
<sl-button type="button" variant="success" name="run">Run</sl-button><br /><br /> | ||
|
||
</form></div> | ||
<sl-divider></sl-divider> | ||
|
||
<div id="compress_stringify-outputs"> | ||
<sl-textarea name="output" label="output" help-text="Output compressed binary"><sl-skeleton effect="none"></sl-skeleton></sl-textarea> | ||
<sl-button variant="neutral" outline name="output-download" disabled>Download</sl-button> | ||
<br /><br /> | ||
</div> | ||
|
||
</sl-tab-panel> | ||
|
||
|
||
<sl-tab-panel name="parse_string_decompress-panel"> | ||
|
||
<small><i>Given a binary or string produced with compress-stringify, decompress and optionally base64 decode.</i></small><br /><br /> | ||
|
||
<div id="parse_string_decompress-inputs"><form action=""> | ||
<sl-input required name="input" type="text" label="input" help-text="Compressed input" disabled></sl-input> | ||
<label for="input-file"><sl-button required variant="primary" outline onclick="this.parentElement.nextElementSibling.click()">Upload</sl-button></label><input type="file" name="input-file" style="display: none"/> | ||
<br /><br /> | ||
<sl-checkbox name="parse-string">parse_string - <i>Parse the input string before decompression</i></sl-checkbox> | ||
<br /><br /> | ||
<sl-divider></sl-divider> | ||
<br /><sl-button name="load-sample-inputs" variant="default" style="display: none;">Load sample inputs</sl-button> | ||
<sl-button type="submit" variant="success" name="run">Run</sl-button><br /><br /> | ||
|
||
</form></div> | ||
<sl-divider></sl-divider> | ||
|
||
<div id="parse_string_decompress-outputs"> | ||
<sl-textarea name="output" label="output" help-text="Output decompressed binary"><sl-skeleton effect="none"></sl-skeleton></sl-textarea> | ||
<sl-button variant="neutral" outline name="output-download" disabled>Download</sl-button> | ||
<br /><br /> | ||
</div> | ||
|
||
</sl-tab-panel> | ||
|
||
|
||
</sl-tab-group> | ||
</div> | ||
|
||
<script type="module" src="./index.js"></script> | ||
|
||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// Generated file. To retain edits, remove this comment. | ||
|
||
function basename(path) { | ||
return path.replace(/.*\//, ''); | ||
} | ||
|
||
async function savePythonScript(pyodide, scriptPath) { | ||
await pyodide.runPythonAsync(` | ||
from pyodide.http import pyfetch | ||
from pathlib import Path | ||
response = await pyfetch("${scriptPath}") | ||
with open(Path("${scriptPath}").name, "wb") as f: | ||
f.write(await response.bytes()) | ||
`) | ||
pyodide.pyimport(basename(scriptPath).replace('.py', '')) | ||
} | ||
|
||
async function main(){ | ||
globalThis.disableInputs('compress_stringify-inputs') | ||
globalThis.disableInputs('parse_string_decompress-inputs') | ||
|
||
const pyodide = await loadPyodide() | ||
await pyodide.loadPackage("numpy") | ||
await pyodide.loadPackage("micropip") | ||
const micropip = pyodide.pyimport("micropip") | ||
await micropip.install("itkwasm-compress-stringify") | ||
|
||
await savePythonScript(pyodide, './compress_stringify_load_sample_inputs.py') | ||
await pyodide.runPythonAsync(await (await fetch("./compress_stringify.py")).text()) | ||
await savePythonScript(pyodide, './parse_string_decompress_load_sample_inputs.py') | ||
await pyodide.runPythonAsync(await (await fetch("./parse_string_decompress.py")).text()) | ||
|
||
globalThis.enableInputs('compress_stringify-inputs') | ||
globalThis.enableInputs('parse_string_decompress-inputs') | ||
|
||
globalThis.pyodide = pyodide | ||
return pyodide | ||
} | ||
const pyodideReady = main() |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.