Skip to content

Commit

Permalink
Initial python library
Browse files Browse the repository at this point in the history
  • Loading branch information
jonmmease committed Jan 30, 2024
1 parent 98dde22 commit 78ca0c2
Show file tree
Hide file tree
Showing 45 changed files with 1,646 additions and 132 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ scratch/
# pixi environments
.pixi

/avenger-python/avenger/_avenger.abi3.so
115 changes: 115 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"avenger-vega",
"avenger-wgpu",
"avenger-vega-test-data",
"avenger-python",
]

resolver = "2"
Expand All @@ -16,4 +17,5 @@ lyon_path = "1.0.1"
lyon_extra = "1.0.1"
lyon = "1.0.1"
image = { version="0.24.8", default-features = false }
lazy_static = "1.4.0"
lazy_static = "1.4.0"
pyo3 = "0.20.2"
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ a non-Vega application, please open an issue.
## Run native
To launch a wgpu rendered visualization in a native window, run the following:
```
cd sg2d-wgpu
cd examples/wgpu-winit
cargo run
```

Expand Down Expand Up @@ -45,7 +45,7 @@ image export, and to VegaFusion to support serverside rendering of large marks.
# Testing
To start with, the most valuable contribution of this project is probably the testing infrastructure. By relying on
vl-convert, a collection of input Vega specs are rendered to PNG and converted to scene graphs. The GPU rendered
PNG images are then compared for similarity to the baselines using structural similarity. See the `gen-test-data`
PNG images are then compared for similarity to the baselines using structural similarity. See the `avenger-vega-test-data`
crate for more information.

Note: These tests aren't running on GitHub Actions yet due to a `MakeWgpuAdapterError` error that
Expand Down
20 changes: 20 additions & 0 deletions avenger-python/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "avenger-python"
version = "0.0.1"
edition = "2021"
license = "BSD-3-Clause"
publish = false

[lib]
name = "avenger"
crate-type = ["cdylib"]

[dependencies]
avenger = { path = "../avenger", features = ["pyo3"] }
avenger-vega = { path = "../avenger-vega", features = ["pyo3"] }
avenger-wgpu = { path = "../avenger-wgpu", features = ["pyo3"] }
pyo3 = { workspace = true, features = ["extension-module", "abi3-py38"] }
pythonize = "0.20.0"
serde = {workspace = true}
pollster = "0.3"
image = {workspace = true}
6 changes: 6 additions & 0 deletions avenger-python/avenger/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from ._avenger import *
from . import altair_utils

# Re-export public members of the Rust _avenger modules
if hasattr(_avenger, "__all__"):
__all__ = _avenger.__all__
48 changes: 48 additions & 0 deletions avenger-python/avenger/altair_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from ._avenger import SceneGraph


def avenger_png_renderer(spec: dict, **kwargs) -> dict:
"""
Altair renderer plugin that uses Avenger to render charts to PNG
This function is registered as avenger-png in the altair.vegalite.v5.renderer
entry point group. It may be enabled in Altair using:
>>> import altair as alt
>>> alt.renderers.enable('avenger-png')
See https://altair-viz.github.io/user_guide/custom_renderers.html
for more information
"""
import altair as alt
import vl_convert as vlc

if alt.data_transformers.active == "vegafusion":
# When the vegafusion transformer is enabled we convert the spec to
# Vega, which will include the pre-transformed inline data
vg_spec = alt.Chart.from_dict(spec).to_dict(format="vega")
vega_sg = vlc.vega_to_scenegraph(vg_spec)
else:
vega_sg = vlc.vegalite_to_scenegraph(spec)

sg = SceneGraph.from_vega_scenegraph(vega_sg)
return {"image/png": sg.to_png(scale=kwargs.get("scale", None))}


def chart_to_png(chart, scale=1) -> bytes:
"""
Convert an altair chart to a png image bytes
:param chart: Altair Chart
:param scale: Scale factor (default 1.0)
:return: png image bytes
"""
import altair as alt
import vl_convert as vlc
if alt.data_transformers.active == "vegafusion":
vg_spec = chart.to_dict(format="vega")
vega_sg = vlc.vega_to_scenegraph(vg_spec)
else:
vl_spec = chart.to_dict(format="vega-lite")
vega_sg = vlc.vegalite_to_scenegraph(vl_spec)
sg = SceneGraph.from_vega_scenegraph(vega_sg)
return sg.to_png(scale=scale)
14 changes: 14 additions & 0 deletions avenger-python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[project]
name = "avenger"
version = "0.0.1"

[tool.maturin]
module-name = "avenger._avenger"

[build-system]
requires = ["maturin>=1.1.0,<2"]
build-backend = "maturin"

# Register Altair renderer plugin
[project.entry-points."altair.vegalite.v5.renderer"]
avenger-png = "avenger.altair_utils:avenger_png_renderer"
50 changes: 50 additions & 0 deletions avenger-python/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use avenger::scene_graph::SceneGraph as RsSceneGraph;
use avenger_vega::scene_graph::VegaSceneGraph;
use avenger_wgpu::canvas::{Canvas, CanvasDimensions, PngCanvas};
use image::{EncodableLayout, ImageOutputFormat};
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use pythonize::depythonize;
use std::io::Cursor;

#[pyclass]
pub struct SceneGraph {
inner: RsSceneGraph,
}

#[pymethods]
impl SceneGraph {
#[staticmethod]
fn from_vega_scenegraph(vega_sg: &PyAny) -> PyResult<Self> {
let vega_sg: VegaSceneGraph = depythonize(vega_sg)?;
let inner = vega_sg.to_scene_graph()?;
Ok(Self { inner })
}

fn to_png(&mut self, py: Python, scale: Option<f32>) -> PyResult<PyObject> {
let mut png_canvas = pollster::block_on(PngCanvas::new(CanvasDimensions {
size: [self.inner.width, self.inner.height],
scale: scale.unwrap_or(1.0),
}))?;

png_canvas.set_scene(&self.inner)?;

let img = pollster::block_on(png_canvas.render())?;
let mut png_data = Vec::new();

img.write_to(&mut Cursor::new(&mut png_data), ImageOutputFormat::Png)
.map_err(|err| {
PyValueError::new_err(format!("Failed to convert image to PNG: {err:?}"))
})?;

Ok(PyObject::from(PyBytes::new(py, png_data.as_bytes())))
}
}

#[pymodule]
fn _avenger(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<SceneGraph>()?;
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
Ok(())
}
12 changes: 6 additions & 6 deletions avenger-vega-test-data/README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
## Generate Test Data
This is a binary crate responsible for generating image baseline vega test data for the sg2d-wgpu renderer.
This is a binary crate responsible for generating image baseline vega test data for the avenger-wgpu renderer.

From the project root
```
cargo run -p sg2d-vega-test-data
cargo run -p avenger-vega-test-data
```

## How it works
A collection of Vega specs are located under `sg2d-vega-test-data/vega-specs` inside category directories.
For example: `sg2d-vega-test-data/vega-specs/rect/stacked_bar.vg.json`.
A collection of Vega specs are located under `avenger-vega-test-data/vega-specs` inside category directories.
For example: `avenger-vega-test-data/vega-specs/rect/stacked_bar.vg.json`.

The main binary entry point scans this directory and for each Vega spec it uses
[`vl-convert-rs`](https://github.com/vega/vl-convert) to
output the following three files in matching category directory under `sg2d-vega-test-data/vega-scenegraphs`
output the following three files in matching category directory under `avenger-vega-test-data/vega-scenegraphs`
1. `{spec_name}.sg.json`: This is the JSON representation of the scenegraph that Vega generates for this Vega spec.
This is used as the input for the wgpu renderer.
2. `{spec_name}.dims.json`: This JSON file contains the final chart width, height, and origin values.
Expand All @@ -21,5 +21,5 @@ output the following three files in matching category directory under `sg2d-vega
chart to SVG and then renders the SVG to PNG using [resvg](https://github.com/RazrFalcon/resvg). This PNG image serves
as the baseline that wgpu rendered PNGs are compared to.

Image baselines are tested in `sg2d-wgpu/tests/test_image_baselines.rs`. Image similarity is measured
Image baselines are tested in `avenger-wgpu/tests/test_image_baselines.rs`. Image similarity is measured
using [DSSIM](https://github.com/kornelski/dssim).
Loading

0 comments on commit 78ca0c2

Please sign in to comment.