From 15936d2e1c5cb7de967735177b73024ecf553855 Mon Sep 17 00:00:00 2001 From: Landa233 <119973446+Landa233@users.noreply.github.com> Date: Mon, 29 Jun 2026 15:57:19 +0200 Subject: [PATCH 1/4] added metadeta --- .github/workflows/ci.yml | 25 +++++++ Cargo.toml | 8 ++- LICENSE | 21 ++++++ README.md | 73 ++++++++++++++++++++ examples/benchmark_top_complexity_scaling.rs | 20 +++--- pyproject.toml | 9 +++ 6 files changed, 144 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 LICENSE create mode 100644 README.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..34291a8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI + +on: + push: + pull_request: + +jobs: + rust: + name: Rust + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + + - name: Check formatting + run: cargo fmt --check + + - name: Run tests + run: cargo test diff --git a/Cargo.toml b/Cargo.toml index 4daa7ac..6a38c21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,12 @@ name = "zigvid" version = "0.1.0" edition = "2024" +description = "Compute zigzag persistence barcodes for binary image sequences and videos." +license = "MIT" +repository = "https://github.com/Landa233/zigvid" +readme = "README.md" +keywords = ["topology", "barcode", "video", "image-processing"] +categories = ["algorithms", "science"] [lib] name = "zigvid" @@ -10,7 +16,6 @@ crate-type = ["rlib", "cdylib"] [features] python = ["pyo3"] - [dependencies] image = "0.25.6" ndarray = "0.16.1" @@ -26,7 +31,6 @@ criterion = "0.5" pyo3 = { version = "0.28.3", optional = true, features = ["extension-module"] } numpy = { version = "0.28.0" } - [dev-dependencies] csv = "1" serde = { version = "1", features = ["derive"] } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5a4b125 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Landa233 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b5af3fe --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# Zigvid + +Zigvid computes zigzag persistence barcodes for binary videos. It provides Rust APIs for the computation of H_0 and H_1, plus optional Python bindings built with PyO3 and maturin. + +## Rust + +Build the project: + +```sh +cargo build +``` + +Run the test suite: + +```sh +cargo test +``` + +Check formatting: + +```sh +cargo fmt --check +``` + +Run an example: + +```sh +cargo run --example ccl_test +``` + +## Python + +The Python package is built from the same Rust crate with maturin. From the repository root, create or activate a virtual environment, then install maturin and build the package in editable mode: + +```sh +python -m pip install maturin +maturin develop +``` + +The Python module exposes `zigvid.compute_barcode_from_paths`, which accepts image paths and keyword options such as foreground color, connectivity, composition type, padding, number of threads, and dimension. + +```python +import zigvid + +barcode = zigvid.compute_barcode_from_paths( + ["frame_00000.png", "frame_00001.png"], + foreground_color="white", + foreground_connectivity="8", + foreground_composition_type="union", + dim=0, +) +``` + +To build a wheel instead: + +```sh +maturin build --release +``` + +## Development Checks + +Before opening a pull request or publishing changes, run: + +```sh +cargo fmt --check +cargo test +``` + +Clippy is useful during cleanup, but it is not yet required by CI because the current codebase still has ordinary warnings to address. + +## License + +Zigvid is licensed under the MIT License. See [LICENSE](LICENSE). diff --git a/examples/benchmark_top_complexity_scaling.rs b/examples/benchmark_top_complexity_scaling.rs index 8b5c5b8..7177d90 100644 --- a/examples/benchmark_top_complexity_scaling.rs +++ b/examples/benchmark_top_complexity_scaling.rs @@ -44,8 +44,9 @@ fn main() -> Result<(), Box> { let pool = ThreadPoolBuilder::new().num_threads(6).build()?; - let num_discs = vec![14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,]; + let num_discs = vec![ + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + ]; let mut rows = Vec::new(); @@ -60,7 +61,7 @@ fn main() -> Result<(), Box> { for run in 1..=repeats { println!(" Run {run}/{repeats}"); - // benches/python/benchmark_videos/top_complexity_scaling/discs_100/256x256_300frames/frames/frame_00000.png + // benches/python/benchmark_videos/top_complexity_scaling/discs_100/256x256_300frames/frames/frame_00000.png let paths: Vec = (0..300) .map(|i| { @@ -84,8 +85,7 @@ fn main() -> Result<(), Box> { let formigram_start = Instant::now(); - let mut h0_formigram = - generate_formigram_from_video(&paths, foreground_info, 1); + let mut h0_formigram = generate_formigram_from_video(&paths, foreground_info, 1); // let mut h1_formigram = // generate_formigram_from_video(&paths, inverted_foreground_info, 1); @@ -104,12 +104,12 @@ fn main() -> Result<(), Box> { total_times.push(total_time); println!("Formigram time: {:.6}s", formigram_time); - println!("Formigram multiplicities: {:?}", h0_formigram.calculate_total_merge_split_mult()); - + println!( + "Formigram multiplicities: {:?}", + h0_formigram.calculate_total_merge_split_mult() + ); } - - let mean_formigram_time = mean(&formigram_times); let std_formigram_time = std(&formigram_times); @@ -123,7 +123,7 @@ fn main() -> Result<(), Box> { rows.push(BenchmarkRow { num_discs: discs, run: run_idx + 1, - threads:1, + threads: 1, formigram_time: formigram_times[run_idx], barcode_time: barcode_times[run_idx], total_time: total_times[run_idx], diff --git a/pyproject.toml b/pyproject.toml index 214e4fc..e898f2a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,8 +5,17 @@ build-backend = "maturin" [project] name = "zigvid" version = "0.1.0" +description = "Python bindings for computing zigzag persistence barcodes from binary image sequences." +readme = "README.md" +license = "MIT" +authors = [{ name = "Landa233" }] requires-python = ">=3.9" +[project.urls] +Homepage = "https://github.com/Landa233/zigvid" +Repository = "https://github.com/Landa233/zigvid" +Issues = "https://github.com/Landa233/zigvid/issues" + [tool.maturin] bindings = "pyo3" python-source = "python" From 076aa0cef7a30298f25456c522e7a8a7481a268b Mon Sep 17 00:00:00 2001 From: Landa233 <119973446+Landa233@users.noreply.github.com> Date: Mon, 29 Jun 2026 16:05:14 +0200 Subject: [PATCH 2/4] new readme and minimal rust example --- README.md | 110 +++++++++++++++++++++++++++++++----- examples/minimal_barcode.rs | 53 +++++++++++++++++ 2 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 examples/minimal_barcode.rs diff --git a/README.md b/README.md index b5af3fe..b15cadc 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,59 @@ # Zigvid -Zigvid computes zigzag persistence barcodes for binary videos. It provides Rust APIs for the computation of H_0 and H_1, plus optional Python bindings built with PyO3 and maturin. +Zigvid computes zigzag persistence barcodes for binary image sequences. It provides a Rust library for building formigrams and computing H0/H1 barcodes, plus optional Python bindings built with PyO3 and maturin. -## Rust +The project is useful when a video can be represented as ordered foreground/background frames and you want to track connected components or holes across time. + +## Features + +- Compute H0 and H1 zigzag persistence barcodes from image frame paths. +- Configure foreground color, 4- or 8-connectivity, and union/intersection frame composition. +- Use the Rust API directly or build a Python extension module with maturin. +- Parallelize frame-level work with a configurable thread count. + +## Repository Status + +This repository is being prepared for public release. The core Rust tests pass, but clippy is not yet enforced in CI because the current codebase still has ordinary warnings to clean up. + +## Rust Usage + +Add Zigvid as a path dependency while developing locally: + +```toml +[dependencies] +zigvid = { path = "../zigvid" } +``` + +Compute H0 barcodes from a sequence of binary image frames: + +```rust +use zigvid::{ + binary_image::foreground_info::{ + BinaryColor, CompositionType, Connectivity, ForegroundInfo, + }, + binary_video::compute_barcode::compute_zigzag_barcode_h0, +}; + +let frames = vec![ + "frames/frame_00000.png", + "frames/frame_00001.png", + "frames/frame_00002.png", +]; + +let foreground = ForegroundInfo { + color: BinaryColor::White, + connectivity: Connectivity::Eight, + composition_type: CompositionType::Union, + padding: None, +}; + +let barcode = compute_zigzag_barcode_h0(&frames, foreground, 4); +println!("{barcode:?}"); +``` + +For H1, use `compute_zigzag_barcode_h1` with the same arguments. + +## Rust Development Build the project: @@ -22,52 +73,83 @@ Check formatting: cargo fmt --check ``` -Run an example: +Run the self-contained example: ```sh -cargo run --example ccl_test +cargo run --example minimal_barcode ``` -## Python +`minimal_barcode` writes temporary test frames before computing barcodes, so it does not require local generated data or benchmark files. + +## Python Usage -The Python package is built from the same Rust crate with maturin. From the repository root, create or activate a virtual environment, then install maturin and build the package in editable mode: +The Python package is built from the same Rust crate with maturin. From the repository root, create or activate a virtual environment, then install the extension in editable mode: ```sh python -m pip install maturin maturin develop ``` -The Python module exposes `zigvid.compute_barcode_from_paths`, which accepts image paths and keyword options such as foreground color, connectivity, composition type, padding, number of threads, and dimension. +Then import `zigvid` from Python: ```python import zigvid barcode = zigvid.compute_barcode_from_paths( - ["frame_00000.png", "frame_00001.png"], + [ + "frames/frame_00000.png", + "frames/frame_00001.png", + "frames/frame_00002.png", + ], foreground_color="white", foreground_connectivity="8", foreground_composition_type="union", - dim=0, + padding=None, + num_threads=-1, + dim=None, ) + +print(barcode) ``` -To build a wheel instead: +`dim=None` computes both supported dimensions. Use `dim=0` for H0 only or `dim=1` for H1 only. + +The return value is a dictionary containing `h0` and/or `h1`. Each dimension contains NumPy arrays grouped by interval endpoint type: + +- `oo`: open-open intervals +- `oc`: open-closed intervals +- `co`: closed-open intervals +- `cc`: closed-closed intervals + +Each array has shape `(n, 2)` and stores birth/death indices. + +## Python Options + +`compute_barcode_from_paths` accepts: + +- `paths`: ordered image frame paths. +- `foreground_color`: `"white"` or `"black"`. +- `foreground_connectivity`: `"4"` or `"8"`. +- `foreground_composition_type`: `"union"` or `"intersection"`. +- `padding`: `None`, `"none"`, `"white"`, or `"black"`. +- `num_threads`: `-1` to use available parallelism, or a positive integer. +- `dim`: `None`, `0`, or `1`. If `None`, both H0 and H1 are computed. + +Build a wheel instead of installing in editable mode: ```sh maturin build --release ``` -## Development Checks +## CI -Before opening a pull request or publishing changes, run: +The GitHub Actions workflow runs on push and pull request with Rust stable: ```sh cargo fmt --check cargo test ``` -Clippy is useful during cleanup, but it is not yet required by CI because the current codebase still has ordinary warnings to address. - ## License Zigvid is licensed under the MIT License. See [LICENSE](LICENSE). diff --git a/examples/minimal_barcode.rs b/examples/minimal_barcode.rs new file mode 100644 index 0000000..4d1d020 --- /dev/null +++ b/examples/minimal_barcode.rs @@ -0,0 +1,53 @@ +use image::{GrayImage, Luma}; +use tempfile::tempdir; +use zigvid::{ + binary_image::foreground_info::{BinaryColor, CompositionType, Connectivity, ForegroundInfo}, + binary_video::compute_barcode::{compute_zigzag_barcode_h0, compute_zigzag_barcode_h1}, +}; + +fn write_frame(path: impl AsRef, pixels: &[(u32, u32)]) -> image::ImageResult<()> { + let mut image = GrayImage::new(5, 5); + + for &(x, y) in pixels { + image.put_pixel(x, y, Luma([255])); + } + + image.save(path) +} + +fn main() -> Result<(), Box> { + let temp_dir = tempdir()?; + + let frames = [ + ("frame_00000.png", vec![(2, 2)]), + ("frame_00001.png", vec![(1, 2), (2, 2), (3, 2)]), + ( + "frame_00002.png", + vec![(1, 1), (1, 2), (2, 2), (3, 2), (3, 3)], + ), + ]; + + let frame_paths = frames + .iter() + .map(|(name, pixels)| { + let path = temp_dir.path().join(name); + write_frame(&path, pixels)?; + Ok(path) + }) + .collect::>>()?; + + let foreground = ForegroundInfo { + color: BinaryColor::White, + connectivity: Connectivity::Eight, + composition_type: CompositionType::Union, + padding: None, + }; + + let h0_barcode = compute_zigzag_barcode_h0(&frame_paths, foreground, 1); + let h1_barcode = compute_zigzag_barcode_h1(&frame_paths, foreground, 1); + + println!("H0 barcode: {h0_barcode:?}"); + println!("H1 barcode: {h1_barcode:?}"); + + Ok(()) +} From 34d3bd8e9bb71551b78558c37bc19c8c9e962c17 Mon Sep 17 00:00:00 2001 From: Landa233 <119973446+Landa233@users.noreply.github.com> Date: Mon, 29 Jun 2026 16:23:26 +0200 Subject: [PATCH 3/4] updated readme --- README.md | 61 ++++++++++++++++++------------------- examples/minimal_barcode.rs | 6 ++-- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index b15cadc..da93a81 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,25 @@ # Zigvid -Zigvid computes zigzag persistence barcodes for binary image sequences. It provides a Rust library for building formigrams and computing H0/H1 barcodes, plus optional Python bindings built with PyO3 and maturin. +Zigvid computes zigzag persistence barcodes for binary videos without constructing cubical complexes. It implements the pipeline of *From Frames to Features: Scalable Zigzag Persistence for Binary Video* (Lanners, 2026) [1], +which reduces the computation of H0-zigzag persistence to a graph problem. These graphs, sometimes called formigrams, are constructed in linear-time in total number of pixels of the video. The graphs are then decomposed into decorated barcodes using the algorithm of Dey and Hou (2022) [2], which runs in near-linear time in number of nodes of that graph. H1 zigzag persistence is recovered via Alexander duality by running the pipeline on the complementary colour with opposite constructions. -The project is useful when a video can be represented as ordered foreground/background frames and you want to track connected components or holes across time. +The resulting implementation is highly parallelisable and makes practical computation of H0 and H1 zigzag persistence on high-resolution, high-frame-rate video data possible. Zigvid provides a Rust library for building formigrams and computing barcodes, together with optional Python bindings built using PyO3 and maturin. ## Features - Compute H0 and H1 zigzag persistence barcodes from image frame paths. - Configure foreground color, 4- or 8-connectivity, and union/intersection frame composition. - Use the Rust API directly or build a Python extension module with maturin. -- Parallelize frame-level work with a configurable thread count. +- Parallelise frame-level work with a configurable thread count. ## Repository Status -This repository is being prepared for public release. The core Rust tests pass, but clippy is not yet enforced in CI because the current codebase still has ordinary warnings to clean up. +This repository is being prepared for public release. The core Rust API is functional, while packaging and convenience features are still under active development. + +### Roadmap +These features will be added: +- Support NumPy arrays as direct input for Python users. +- Provide pre-built wheels for installation via `pip`. ## Rust Usage @@ -24,14 +30,14 @@ Add Zigvid as a path dependency while developing locally: zigvid = { path = "../zigvid" } ``` -Compute H0 barcodes from a sequence of binary image frames: +Compute H0 and H1 barcodes from a sequence of binary image frames: ```rust use zigvid::{ binary_image::foreground_info::{ BinaryColor, CompositionType, Connectivity, ForegroundInfo, }, - binary_video::compute_barcode::compute_zigzag_barcode_h0, + binary_video::compute_barcode::{compute_zigzag_barcode_h0, compute_zigzag_barcode_h1}, }; let frames = vec![ @@ -47,31 +53,18 @@ let foreground = ForegroundInfo { padding: None, }; -let barcode = compute_zigzag_barcode_h0(&frames, foreground, 4); -println!("{barcode:?}"); -``` - -For H1, use `compute_zigzag_barcode_h1` with the same arguments. - -## Rust Development - -Build the project: - -```sh -cargo build -``` +let number_of_cores = 4; -Run the test suite: +let h0_barcode = compute_zigzag_barcode_h0(&frames, foreground, number_of_cores); +let h1_barcode = compute_zigzag_barcode_h1(&frames, foreground, number_of_cores); -```sh -cargo test +println!("H0 barcode: {h0_barcode:?}"); +println!("H1 barcode: {h1_barcode:?}"); ``` -Check formatting: +For H1, pass the same `ForegroundInfo`; `compute_zigzag_barcode_h1` applies the required foreground/background inversion internally. -```sh -cargo fmt --check -``` +## Rust Development Run the self-contained example: @@ -141,14 +134,18 @@ Build a wheel instead of installing in editable mode: maturin build --release ``` -## CI -The GitHub Actions workflow runs on push and pull request with Rust stable: +## References -```sh -cargo fmt --check -cargo test -``` +[1] D. Lanners, +*From Frames to Features: Scalable Zigzag Persistence for Binary Video*, +2026. +[[arXiv]](https://arxiv.org/abs/...) + +[2] T. K. Dey and T. Hou, +*Computing Zigzag Persistence on Graphs in Near-Linear Time*, +Proceedings of the 38th International Symposium on Computational Geometry (SoCG), 2022. +[[DOI]](https://doi.org/10.4230/LIPIcs.SoCG.2022.31) ## License diff --git a/examples/minimal_barcode.rs b/examples/minimal_barcode.rs index 4d1d020..8c75fe4 100644 --- a/examples/minimal_barcode.rs +++ b/examples/minimal_barcode.rs @@ -43,8 +43,10 @@ fn main() -> Result<(), Box> { padding: None, }; - let h0_barcode = compute_zigzag_barcode_h0(&frame_paths, foreground, 1); - let h1_barcode = compute_zigzag_barcode_h1(&frame_paths, foreground, 1); + let number_of_cores = 1; + + let h0_barcode = compute_zigzag_barcode_h0(&frame_paths, foreground, number_of_cores); + let h1_barcode = compute_zigzag_barcode_h1(&frame_paths, foreground, number_of_cores); println!("H0 barcode: {h0_barcode:?}"); println!("H1 barcode: {h1_barcode:?}"); From 6fbbb9374db516909e392071be2222dd0611db1d Mon Sep 17 00:00:00 2001 From: Landa233 <119973446+Landa233@users.noreply.github.com> Date: Mon, 29 Jun 2026 16:28:00 +0200 Subject: [PATCH 4/4] updated intro of readme --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index da93a81..a7f133d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # Zigvid -Zigvid computes zigzag persistence barcodes for binary videos without constructing cubical complexes. It implements the pipeline of *From Frames to Features: Scalable Zigzag Persistence for Binary Video* (Lanners, 2026) [1], -which reduces the computation of H0-zigzag persistence to a graph problem. These graphs, sometimes called formigrams, are constructed in linear-time in total number of pixels of the video. The graphs are then decomposed into decorated barcodes using the algorithm of Dey and Hou (2022) [2], which runs in near-linear time in number of nodes of that graph. H1 zigzag persistence is recovered via Alexander duality by running the pipeline on the complementary colour with opposite constructions. +Zigvid computes H0 and H1 zigzag persistence barcodes for binary videos. It implements the pipeline of [1], providing a highly parallelisable graph-based approach that avoids constructing cubical complexes and scales to high-resolution, high-frame-rate data. The barcode decomposition is based on the algorithm presented in [2]. -The resulting implementation is highly parallelisable and makes practical computation of H0 and H1 zigzag persistence on high-resolution, high-frame-rate video data possible. Zigvid provides a Rust library for building formigrams and computing barcodes, together with optional Python bindings built using PyO3 and maturin. +The package provides a Rust library for building formigrams and computing barcodes, together with optional Python bindings built using PyO3 and maturin. ## Features @@ -56,6 +55,8 @@ let foreground = ForegroundInfo { let number_of_cores = 4; let h0_barcode = compute_zigzag_barcode_h0(&frames, foreground, number_of_cores); + +// Uses Alexander duality under the hood let h1_barcode = compute_zigzag_barcode_h1(&frames, foreground, number_of_cores); println!("H0 barcode: {h0_barcode:?}");