diff --git a/.github/workflows/publish-test.yml b/.github/workflows/publish-test.yml index 7d830de..f4237b4 100644 --- a/.github/workflows/publish-test.yml +++ b/.github/workflows/publish-test.yml @@ -24,7 +24,7 @@ jobs: import tomllib from pathlib import Path project = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))["project"] - assert project["name"] == "polyplot", project["name"] + assert project["name"] == "polyrender", project["name"] print("package:", project["name"]) print("version:", project["version"]) PY diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f43c7d7..a4f526c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -31,7 +31,7 @@ jobs: import tomllib from pathlib import Path project = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))["project"] - assert project["name"] == "polyplot", project["name"] + assert project["name"] == "polyrender", project["name"] print("package:", project["name"]) print("version:", project["version"]) PY diff --git a/.gitignore b/.gitignore index 81946e8..b2cb2e5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,8 @@ dist/ build/ # Caches and local artifacts -.polyplot/ +.polyrender/ +.polyplot/ # legacy cache dir name .DS_Store # Editor / local agent config (not part of the published package) diff --git a/README.md b/README.md index b46d6f9..fb50928 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# polyplot +# polyrender `Three Fiber`–based [anywidget](https://anywidget.dev/) for **2.5D** polygons: preprocess stacked 2D outlines (per `cell_id` and `ZIndex`), build triangulated meshes, export spatial GLB tiles, and view them in the browser with a local tile server and WebGL widget. -![polyplot viewer showing colorful 3D cell meshes](assets/viewer.jpg) +![polyrender viewer showing colorful 3D cell meshes](assets/viewer.jpg) -**Documentation (Zensical):** [ckmah.github.io/polyplot](https://ckmah.github.io/polyplot/) +**Documentation (Zensical):** [ckmah.github.io/polyrender](https://ckmah.github.io/polyrender/) ## Requirements @@ -38,18 +38,18 @@ uv sync --group dev uv run marimo edit quickstart.py ``` -[![Open in molab](https://marimo.io/molab-shield.svg)](https://molab.marimo.io/github/ckmah/polyplot/blob/main/quickstart.py) +[![Open in molab](https://marimo.io/molab-shield.svg)](https://molab.marimo.io/github/ckmah/polyrender/blob/main/quickstart.py) ## Usage ```python -import polyplot as po +import polyrender as po -tiles_info = po.meshify(gdf) # default cache: ./.polyplot// +tiles_info = po.meshify(gdf) # default cache: ./.polyrender// po.plot(gdf) # meshifies from cache if needed; wireframe / opacity / BG in the viewer UI ``` -- **`meshify`**: preprocess a GeoDataFrame (`cell_id`, `ZIndex`, `geometry`), write `tiles/` and `tiles.json` under `.polyplot//` by default (override with `out_dir=...`). Use `smooth=False` for no Taubin smoothing, `use_cache=False` to force a rebuild. +- **`meshify`**: preprocess a GeoDataFrame (`cell_id`, `ZIndex`, `geometry`), write `tiles/` and `tiles.json` under `.polyrender//` by default (override with `out_dir=...`). Use `smooth=False` for no Taubin smoothing, `use_cache=False` to force a rebuild. - **`plot`**: calls `meshify` when needed, starts or reuses a local tile server, and returns a marimo `anywidget` viewer. Wireframe, opacity, and background are adjusted in the widget toolbar, not via Python. ## Full demo (development notebook) @@ -70,10 +70,10 @@ uv run python scripts/make_liver_subset.py ## Development -The Python package lives in the `polyplot/` directory. Optional: install [gltfpack](https://github.com/zeux/meshopt) on your PATH for smaller GLB files (compression is enabled inside `meshify`). +The Python package lives in the `polyrender/` directory. Optional: install [gltfpack](https://github.com/zeux/meshopt) on your PATH for smaller GLB files (compression is enabled inside `meshify`). ## Repository -[github.com/ckmah/polyplot](https://github.com/ckmah/polyplot) +[github.com/ckmah/polyrender](https://github.com/ckmah/polyrender) To publish pre-rendered molab sessions, run from the repo root: `uvx marimo export session quickstart.py` (and similarly for `dev/full_demo.py` if desired). diff --git a/autoresearch/README.md b/autoresearch/README.md index 40cf0f5..f17721b 100644 --- a/autoresearch/README.md +++ b/autoresearch/README.md @@ -1,6 +1,6 @@ # Autoresearch -Experiment drivers and benchmarks for the meshify pipeline. This tree is **not** part of the installable `polyplot` package. +Experiment drivers and benchmarks for the meshify pipeline. This tree is **not** part of the installable `polyrender` package. | Path | Role | |------|------| diff --git a/autoresearch/scripts/autoresearch_1k.py b/autoresearch/scripts/autoresearch_1k.py index fc4644b..a27252c 100644 --- a/autoresearch/scripts/autoresearch_1k.py +++ b/autoresearch/scripts/autoresearch_1k.py @@ -25,7 +25,7 @@ REPO_ROOT = AUTORESEARCH_DIR.parent _LOG_DIR = AUTORESEARCH_DIR / "logs" _LOG_DIR.mkdir(parents=True, exist_ok=True) -MB = REPO_ROOT / "polyplot" / "_mesh_build.py" +MB = REPO_ROOT / "polyrender" / "_mesh_build.py" RESULTS = AUTORESEARCH_DIR / "results.tsv" LOG = _LOG_DIR / "autoresearch_1k.log" MEASURE = _SCRIPT_DIR / "meshify_benchmark_measure.py" @@ -150,10 +150,10 @@ def main() -> None: _append(exp_id, "-", 0.0, "skip", desc + " (no regex match)") continue MB.write_text(new, encoding="utf-8") - _git("add", "polyplot/_mesh_build.py") + _git("add", "polyrender/_mesh_build.py") c = _git("commit", "-m", f"experiment {exp_id}: mesh W={win} C={cap} F={fft_th}") if c.returncode != 0: - _git("checkout", "HEAD", "--", "polyplot/_mesh_build.py") + _git("checkout", "HEAD", "--", "polyrender/_mesh_build.py") _append(exp_id, "-", 0.0, "skip", desc + " commit_fail") continue commit = (_git("rev-parse", "--short", "HEAD").stdout or "").strip() diff --git a/autoresearch/scripts/autoresearch_alg.py b/autoresearch/scripts/autoresearch_alg.py index bb4595a..8584368 100644 --- a/autoresearch/scripts/autoresearch_alg.py +++ b/autoresearch/scripts/autoresearch_alg.py @@ -21,8 +21,8 @@ REPO_ROOT = AUTORESEARCH_DIR.parent _LOG_DIR = AUTORESEARCH_DIR / "logs" _LOG_DIR.mkdir(parents=True, exist_ok=True) -MB = REPO_ROOT / "polyplot" / "_mesh_build.py" -TP = REPO_ROOT / "polyplot" / "_tile_export.py" +MB = REPO_ROOT / "polyrender" / "_mesh_build.py" +TP = REPO_ROOT / "polyrender" / "_tile_export.py" RESULTS = AUTORESEARCH_DIR / "results.tsv" LOG = _LOG_DIR / "autoresearch_200.log" MEASURE = _SCRIPT_DIR / "meshify_benchmark_measure.py" diff --git a/autoresearch/scripts/run_experiments.py b/autoresearch/scripts/run_experiments.py index 49756fc..2db3dab 100644 --- a/autoresearch/scripts/run_experiments.py +++ b/autoresearch/scripts/run_experiments.py @@ -10,7 +10,7 @@ REPO_ROOT = AUTORESEARCH_DIR.parent _LOG_DIR = AUTORESEARCH_DIR / "logs" _LOG_DIR.mkdir(parents=True, exist_ok=True) -MB = REPO_ROOT / "polyplot" / "_mesh_build.py" +MB = REPO_ROOT / "polyrender" / "_mesh_build.py" RESULTS = AUTORESEARCH_DIR / "results.tsv" LOG = _LOG_DIR / "autoresearch_run.log" MEASURE = _SCRIPT_DIR / "meshify_benchmark_measure.py" @@ -474,7 +474,7 @@ def _cap_flat(ring_xy, base_idx, want_positive_z): ] -TE = REPO_ROOT / "polyplot" / "_tile_export.py" +TE = REPO_ROOT / "polyrender" / "_tile_export.py" # ================================================================ # BATCH 5: Numba warmup, shapely coords bypass, strip vectorise, @@ -750,8 +750,8 @@ def _largest_polygon(geom): ] -CA = REPO_ROOT / "polyplot" / "_cache.py" -TE = REPO_ROOT / "polyplot" / "_tile_export.py" +CA = REPO_ROOT / "polyrender" / "_cache.py" +TE = REPO_ROOT / "polyrender" / "_tile_export.py" # ================================================================ # BATCH 6: Profile-driven optimizations after exp 149. @@ -848,8 +848,8 @@ def _cell_max_turning(rings: list[np.ndarray]) -> float: # -------------------------------------------------------- ("gdf_cache_key vectorised WKB + repr-bulk id/ZIndex hash", [ (CA, - '_CACHE_VERSION = b"polyplot_cache_v5\\n"', - '_CACHE_VERSION = b"polyplot_cache_v6\\n"'), + '_CACHE_VERSION = b"polyrender_cache_v5\\n"', + '_CACHE_VERSION = b"polyrender_cache_v6\\n"'), (CA, """\ def gdf_cache_key(gdf, smooth: bool) -> str: @@ -1120,7 +1120,7 @@ def _arc_resample_closed(ring: np.ndarray, n_points: int) -> np.ndarray: ] -PP = REPO_ROOT / "polyplot" / "_preprocess.py" +PP = REPO_ROOT / "polyrender" / "_preprocess.py" # ================================================================ # BATCH 7: curvature_resample_nb + simplify topology + cell_max_turning @@ -1448,7 +1448,7 @@ def _cell_max_turning(rings: list[np.ndarray]) -> float: ] -PP = REPO_ROOT / "polyplot" / "_preprocess.py" +PP = REPO_ROOT / "polyrender" / "_preprocess.py" EXPERIMENTS_8 = [ @@ -2319,7 +2319,7 @@ def _refine_shift_nb(prev: np.ndarray, curr: np.ndarray, k0: int, win: int) -> i ] -TE = REPO_ROOT / "polyplot" / "_tile_export.py" +TE = REPO_ROOT / "polyrender" / "_tile_export.py" EXPERIMENTS_12 = [ @@ -2463,8 +2463,8 @@ def _collect_score(cid): ] -TE = REPO_ROOT / "polyplot" / "_tile_export.py" -MB = REPO_ROOT / "polyplot" / "_mesh_build.py" +TE = REPO_ROOT / "polyrender" / "_tile_export.py" +MB = REPO_ROOT / "polyrender" / "_mesh_build.py" EXPERIMENTS_13 = [ @@ -2525,7 +2525,7 @@ def _build_glb_bytes( pmax = positions_f32.max(axis=0).tolist() n_idx = int(len(indices_u32)) gltf_json = _json.dumps({ - "asset": {"version": "2.0", "generator": "polyplot"}, + "asset": {"version": "2.0", "generator": "polyrender"}, "scene": 0, "scenes": [{"nodes": [0]}], "nodes": [{"mesh": 0}], "meshes": [{"primitives": [{"attributes": {"POSITION": 0, "COLOR_0": 2, "NORMAL": 3}, "indices": 1, "mode": 4}]}], @@ -2646,7 +2646,7 @@ def _arc_resample_nb(ring: np.ndarray, n_points: int) -> np.ndarray:"""), ] -TE = REPO_ROOT / "polyplot" / "_tile_export.py" +TE = REPO_ROOT / "polyrender" / "_tile_export.py" EXPERIMENTS_14 = [ @@ -2735,7 +2735,7 @@ def _build_glb_bytes( pmax = positions_f32.max(axis=0).tolist() n_idx = int(len(indices_u32)) gltf_json = _json.dumps({ - "asset": {"version": "2.0", "generator": "polyplot"}, + "asset": {"version": "2.0", "generator": "polyrender"}, "scene": 0, "scenes": [{"nodes": [0]}], "nodes": [{"mesh": 0}], "meshes": [{"primitives": [{"attributes": {"POSITION": 0, "COLOR_0": 2, "NORMAL": 3}, "indices": 1, "mode": 4}]}], @@ -2805,7 +2805,7 @@ def _build_glb_bytes( ] -TE = REPO_ROOT / "polyplot" / "_tile_export.py" +TE = REPO_ROOT / "polyrender" / "_tile_export.py" # ================================================================ # BATCH 15: Two-phase parallel gltfpack (sequential build + parallel compress) @@ -3009,7 +3009,7 @@ def _build_glb_bytes( pmax = positions_f32.max(axis=0).tolist() n_idx = int(len(indices_u32)) gltf_json = _json.dumps({ - "asset": {"version": "2.0", "generator": "polyplot"}, + "asset": {"version": "2.0", "generator": "polyrender"}, "scene": 0, "scenes": [{"nodes": [0]}], "nodes": [{"mesh": 0}], "meshes": [{"primitives": [{"attributes": {"POSITION": 0, "COLOR_0": 2, "NORMAL": 3}, "indices": 1, "mode": 4}]}], @@ -3099,8 +3099,8 @@ def _build_glb_bytes( ] -TE = REPO_ROOT / "polyplot" / "_tile_export.py" -MB = REPO_ROOT / "polyplot" / "_mesh_build.py" +TE = REPO_ROOT / "polyrender" / "_tile_export.py" +MB = REPO_ROOT / "polyrender" / "_mesh_build.py" # ================================================================ # BATCH 16: ring_target reduction + bulk array extract for collect_rings @@ -3141,8 +3141,8 @@ def _build_glb_bytes( " return rings_2d, zs\n\n\n@njit(cache=True, fastmath=True, nogil=True)\ndef _cell_max_turning_nb(", " return rings_2d, zs\n\n\ndef _collect_rings_arrays(\n geoms: np.ndarray, zvals: np.ndarray, z_scale: float\n) -> tuple[list[np.ndarray], list[float]]:\n \"\"\"Like _collect_rings_for_cell but accepts pre-extracted numpy arrays.\"\"\"\n polys: list = []\n zi_keep: list[float] = []\n for geom, zi in zip(geoms, zvals, strict=True):\n p = _largest_polygon(geom)\n if p is not None:\n polys.append(p)\n zi_keep.append(zi)\n if not polys:\n return [], []\n ext_rings = _shapely.get_exterior_ring(np.asarray(polys, dtype=object))\n npts = _shapely.get_num_coordinates(ext_rings)\n coords_all = _shapely.get_coordinates(ext_rings, include_z=False)\n rings_2d: list[np.ndarray] = []\n zs: list[float] = []\n offset = 0\n for n, zi in zip(npts, zi_keep):\n nc = int(n)\n if nc < 4:\n offset += nc\n continue\n pts = coords_all[offset:offset + nc - 1]\n offset += nc\n if len(pts) < 3:\n continue\n area2 = (pts[:-1, 0] * pts[1:, 1] - pts[1:, 0] * pts[:-1, 1]).sum()\n area2 += pts[-1, 0] * pts[0, 1] - pts[0, 0] * pts[-1, 1]\n if area2 < 0:\n pts = pts[::-1]\n rings_2d.append(pts)\n zs.append(float(zi) * float(z_scale))\n return rings_2d, zs\n\n\n@njit(cache=True, fastmath=True, nogil=True)\ndef _cell_max_turning_nb("), (TE, - "from polyplot._mesh_build import (\n _adaptive_ring_targets_from_scores,\n _cell_max_turning,\n _collect_rings_for_cell,\n build_loft_mesh_from_rings,\n cell_color,\n)", - "from polyplot._mesh_build import (\n _adaptive_ring_targets_from_scores,\n _cell_max_turning,\n _collect_rings_arrays,\n build_loft_mesh_from_rings,\n cell_color,\n)"), + "from polyrender._mesh_build import (\n _adaptive_ring_targets_from_scores,\n _cell_max_turning,\n _collect_rings_for_cell,\n build_loft_mesh_from_rings,\n cell_color,\n)", + "from polyrender._mesh_build import (\n _adaptive_ring_targets_from_scores,\n _cell_max_turning,\n _collect_rings_arrays,\n build_loft_mesh_from_rings,\n cell_color,\n)"), (TE, " # Group once: avoids O(n_cells * n_rows) boolean slicing.\n groups = {cid: df for cid, df in gdf_render.groupby(\"cell_id\", sort=False)}\n\n # Pre-extract rings once per cell (used for adaptive sizing + meshing).\n z_scale = cfg.get(\"z_scale\", 2.0)\n rings_by_cid: dict = {}\n zs_by_cid: dict = {}\n scores_by_cid: dict = {}\n for cid in all_cell_ids:\n rings, zs = _collect_rings_for_cell(groups[cid], z_scale)\n rings_by_cid[cid] = rings\n zs_by_cid[cid] = zs\n scores_by_cid[cid] = _cell_max_turning(rings) if rings else 0.0", " # Bulk-extract geometry/ZIndex arrays once (avoids 1000 slow pandas __getitem__ calls\n # and eliminates the groupby overhead).\n z_scale = cfg.get(\"z_scale\", 2.0)\n _geoms_col = gdf_render.geometry.values\n _zvals_col = gdf_render[\"ZIndex\"].to_numpy(dtype=np.float64)\n _cids_col = gdf_render[\"cell_id\"].values\n _geoms_map: dict = defaultdict(list)\n _zvals_map: dict = defaultdict(list)\n for _cid, _g, _z in zip(_cids_col, _geoms_col, _zvals_col):\n _geoms_map[_cid].append(_g)\n _zvals_map[_cid].append(_z)\n rings_by_cid: dict = {}\n zs_by_cid: dict = {}\n scores_by_cid: dict = {}\n for cid in all_cell_ids:\n rings, zs = _collect_rings_arrays(\n np.asarray(_geoms_map[cid]),\n np.asarray(_zvals_map[cid], dtype=np.float64),\n z_scale,\n )\n rings_by_cid[cid] = rings\n zs_by_cid[cid] = zs\n scores_by_cid[cid] = _cell_max_turning(rings) if rings else 0.0"), @@ -3170,7 +3170,7 @@ def _build_glb_bytes( ] -TE = REPO_ROOT / "polyplot" / "_tile_export.py" +TE = REPO_ROOT / "polyrender" / "_tile_export.py" # ================================================================ # BATCH 17: Key fix - only patch _tile_export.py to avoid Numba cache invalidation. @@ -3256,8 +3256,8 @@ def _build_glb_bytes( # --------------------------------------------------------------- ("tile_export: inline bulk ring extraction (skip groupby + pandas per-cell)", [ (TE, - "from polyplot._mesh_build import (\n _adaptive_ring_targets_from_scores,\n _cell_max_turning,\n _collect_rings_for_cell,\n build_loft_mesh_from_rings,\n cell_color,\n)", - "import shapely as _shapely\n\nfrom polyplot._mesh_build import (\n _adaptive_ring_targets_from_scores,\n _cell_max_turning,\n _largest_polygon,\n build_loft_mesh_from_rings,\n cell_color,\n)"), + "from polyrender._mesh_build import (\n _adaptive_ring_targets_from_scores,\n _cell_max_turning,\n _collect_rings_for_cell,\n build_loft_mesh_from_rings,\n cell_color,\n)", + "import shapely as _shapely\n\nfrom polyrender._mesh_build import (\n _adaptive_ring_targets_from_scores,\n _cell_max_turning,\n _largest_polygon,\n build_loft_mesh_from_rings,\n cell_color,\n)"), (TE, _BULK_OLD, _BULK_NEW), @@ -3304,7 +3304,7 @@ def _build_glb_bytes( ] -TE = REPO_ROOT / "polyplot" / "_tile_export.py" +TE = REPO_ROOT / "polyrender" / "_tile_export.py" # ================================================================ # BATCH 18: Continue ring_target reduction + misc TE-only optimizations. @@ -3586,9 +3586,9 @@ def _build_glb_bytes( ] -TE = REPO_ROOT / "polyplot" / "_tile_export.py" -PP = REPO_ROOT / "polyplot" / "_preprocess.py" -CA = REPO_ROOT / "polyplot" / "_cache.py" +TE = REPO_ROOT / "polyrender" / "_tile_export.py" +PP = REPO_ROOT / "polyrender" / "_preprocess.py" +CA = REPO_ROOT / "polyrender" / "_cache.py" # ================================================================ # BATCH 19: Profiling reveals: @@ -3767,7 +3767,7 @@ def _build_glb_bytes( ] -_API = REPO_ROOT / "polyplot" / "_api.py" +_API = REPO_ROOT / "polyrender" / "_api.py" # ================================================================ # BATCH 20: Key findings: diff --git a/dev/full_demo.py b/dev/full_demo.py index 723926d..781dcbf 100644 --- a/dev/full_demo.py +++ b/dev/full_demo.py @@ -4,7 +4,7 @@ # "marimo>=0.22", # "geopandas>=1.0", # "pyarrow", -# "polyplot @ git+https://github.com/ckmah/polyplot.git@ec17ca181279b30b4b2befddb15d5c15d118b3b0", +# "polyrender @ git+https://github.com/ckmah/polyrender.git@ed2f47e954ecf0ac846bca10668a2754b1a7725e", # ] # /// @@ -19,11 +19,11 @@ def _(): import os import geopandas as gpd - import polyplot as po + import polyrender as po parquet_url = os.getenv( - "POLYPLOT_PARQUET_URL", - "https://huggingface.co/datasets/ckmah/polyplot/resolve/92678be92f8e0b06fc2a32b53885c4fdf3419ee3/liver_crop.parquet", + "POLYRENDER_PARQUET_URL", + "https://huggingface.co/datasets/ckmah/polyrender/resolve/92678be92f8e0b06fc2a32b53885c4fdf3419ee3/liver_crop.parquet", ) return gpd, parquet_url, po diff --git a/docs/get-started.md b/docs/get-started.md index 61709fd..481bf7b 100644 --- a/docs/get-started.md +++ b/docs/get-started.md @@ -8,8 +8,8 @@ ## Install ```bash -git clone https://github.com/ckmah/polyplot.git -cd polyplot +git clone https://github.com/ckmah/polyrender.git +cd polyrender uv sync ``` @@ -38,13 +38,13 @@ Larger local parquets in `sample_data/` are not tracked by git (see `sample_data ## Quick start (marimo) -The shortest path is [`quickstart.py`](https://github.com/ckmah/polyplot/blob/main/quickstart.py) at the repository root (marimo). From a clone of the repository: +The shortest path is [`quickstart.py`](https://github.com/ckmah/polyrender/blob/main/quickstart.py) at the repository root (marimo). From a clone of the repository: ```bash uv run marimo edit quickstart.py ``` -A **“Open in molab”** badge in the [README](https://github.com/ckmah/polyplot#readme) links to a hosted run once the file is on the default branch. +A **“Open in molab”** badge in the [README](https://github.com/ckmah/polyrender#readme) links to a hosted run once the file is on the default branch. ## Full demo @@ -64,4 +64,4 @@ Open the URL printed in the terminal (by default [http://127.0.0.1:8000](http:// ## Publish -Documentation for this site is built with [Zensical](https://zensical.org/docs/) in CI and published to **GitHub Pages** at the [`site_url`](https://ckmah.github.io/polyplot/) configured in `zensical.toml`. +Documentation for this site is built with [Zensical](https://zensical.org/docs/) in CI and published to **GitHub Pages** at the [`site_url`](https://ckmah.github.io/polyrender/) configured in `zensical.toml`. diff --git a/docs/index.md b/docs/index.md index 7d9bb7f..a90214e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,19 +2,19 @@ icon: lucide/rocket --- -# Polyplot +# Polyrender -**Polyplot** is a [Three.js](https://threejs.org/) (React Three Fiber) [anywidget](https://anywidget.dev/) for **2.5D** polygon stacks: it preprocesses stacked 2D outlines per `cell_id` and `ZIndex`, builds triangulated meshes, exports **GLB** tiles, and serves them to a **WebGL** viewer in the browser (marimo, Jupyter, and similar). +**Polyrender** is a [Three.js](https://threejs.org/) (React Three Fiber) [anywidget](https://anywidget.dev/) for **2.5D** polygon stacks: it preprocesses stacked 2D outlines per `cell_id` and `ZIndex`, builds triangulated meshes, exports **GLB** tiles, and serves them to a **WebGL** viewer in the browser (marimo, Jupyter, and similar). - [**Get started**](get-started.md) – install, sample data, and the quickstart notebook. - [**User guide**](user-guide.md) – `meshify`, `plot`, and caching. -- [PyPI / source](https://github.com/ckmah/polyplot) – `uv sync` from the repository root (`--extra test` / `--extra docs` as needed); API docstrings in `polyplot/`. +- [PyPI / source](https://github.com/ckmah/polyrender) – `uv sync` from the repository root (`--extra test` / `--extra docs` as needed); API docstrings in `polyrender/`. ## At a glance | Piece | Role | |-------|------| -| `meshify(gdf, ...)` | Preprocess, tile, and write `tiles.json` and GLB shards under a content-hashed directory (default `.polyplot/`) | +| `meshify(gdf, ...)` | Preprocess, tile, and write `tiles.json` and GLB shards under a content-hashed directory (default `.polyrender/`) | | `plot(gdf, ...)` | Ensure tiles exist, run a local tile server, and return a marimo `anywidget` WebGL view | The viewer controls **wireframe**, **opacity**, and **background** in the UI; they are not set from Python. diff --git a/docs/user-guide.md b/docs/user-guide.md index 7efa5eb..73a0f05 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -2,7 +2,7 @@ ## Data model -`polyplot` expects a [GeoDataFrame](https://geopandas.org/) with at least: +`polyrender` expects a [GeoDataFrame](https://geopandas.org/) with at least: - `cell_id` – stable identifier for each biological cell (or object). - `ZIndex` – ordering along the stack (height or slice). @@ -14,19 +14,19 @@ Each row is one 2D outline; multiple rows per `cell_id` with different `ZIndex` ```python import geopandas as gpd -import polyplot as po +import polyrender as po gdf = gpd.read_parquet("sample_data/liver_crop_sample.parquet") info = po.meshify( gdf, - out_dir=".polyplot", + out_dir=".polyrender", smooth=True, use_cache=True, show_progress=True, ) ``` -- Writes `**tiles.json**` and per-tile **GLB** files under a content-hashed subfolder of `out_dir` (default `.polyplot`). +- Writes `**tiles.json**` and per-tile **GLB** files under a content-hashed subfolder of `out_dir` (default `.polyrender`). - `smooth` – 3D Taubin smoothing (on) or raw surfaces (off). - `use_cache` – if the same fingerprint was built before, reuses the cache and sets `_cache_hit` in the return value. - `show_progress` – in marimo, a progress display while building. diff --git a/polyplot/__init__.py b/polyplot/__init__.py deleted file mode 100644 index 73ce3ee..0000000 --- a/polyplot/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""polyplot — 3D cross-section visualization for segmentation geometries.""" - -from polyplot._api import MeshifyInfo, TileRecord, meshify, plot -from polyplot._widget import PolyFiberWidget - -__all__ = ["MeshifyInfo", "PolyFiberWidget", "TileRecord", "meshify", "plot"] diff --git a/polyrender/__init__.py b/polyrender/__init__.py new file mode 100644 index 0000000..5553e17 --- /dev/null +++ b/polyrender/__init__.py @@ -0,0 +1,6 @@ +"""polyrender — 3D cross-section visualization for segmentation geometries.""" + +from polyrender._api import MeshifyInfo, TileRecord, meshify, plot +from polyrender._widget import PolyRenderWidget + +__all__ = ["MeshifyInfo", "PolyRenderWidget", "TileRecord", "meshify", "plot"] diff --git a/polyplot/_api.py b/polyrender/_api.py similarity index 93% rename from polyplot/_api.py rename to polyrender/_api.py index 7e1350a..1e4fc8d 100644 --- a/polyplot/_api.py +++ b/polyrender/_api.py @@ -34,7 +34,7 @@ class MeshifyInfo(TypedDict): def meshify( gdf: gpd.GeoDataFrame, - out_dir: str | Path = ".polyplot", + out_dir: str | Path = ".polyrender", *, smooth: bool = True, use_cache: bool = True, @@ -51,7 +51,7 @@ def meshify( Args: gdf: GeoDataFrame with columns ``cell_id``, ``ZIndex``, and ``geometry``. - out_dir: Root cache directory (default ``.polyplot`` under the process cwd). + out_dir: Root cache directory (default ``.polyrender`` under the process cwd). smooth: If ``True``, apply 3D Taubin smoothing (1 iteration); if ``False``, none. use_cache: If ``True`` and this fingerprint already exists, load ``tiles.json`` and skip rebuild. show_progress: If ``True`` and running in marimo, show a progress bar while building tiles. @@ -64,9 +64,9 @@ def meshify( ``tiles.json`` mtime, capped). The current digest and any shard still served by an active :func:`plot` tile server are never removed. """ - from polyplot._cache import gdf_cache_key, prune_stale_cache_shards - from polyplot._preprocess import preprocess_gdf - from polyplot._tile_export import export_tiles + from polyrender._cache import gdf_cache_key, prune_stale_cache_shards + from polyrender._preprocess import preprocess_gdf + from polyrender._tile_export import export_tiles root = Path(out_dir).expanduser().resolve() fp = gdf_cache_key(gdf, smooth) @@ -140,12 +140,12 @@ def plot( max_concurrent_fetches: Maximum parallel HTTP fetches for tile GLBs. Returns: - A marimo ``anywidget`` UI element wrapping :class:`~polyplot.PolyFiberWidget`. + A marimo ``anywidget`` UI element wrapping :class:`~polyrender.PolyRenderWidget`. """ import base64 import marimo as mo import numpy as np - from polyplot._widget import PolyFiberWidget + from polyrender._widget import PolyRenderWidget # Minimap payload: centroid per cell_id (XY only), packed as float32 then base64. # Use area-weighted centroids across slices to better match the full footprint. @@ -164,11 +164,11 @@ def plot( centroids_xy_b64 = base64.b64encode(cxy.tobytes()).decode("ascii") centroids_cell_ids_json = json.dumps([str(x) for x in grp.index.tolist()], separators=(",", ":")) - from polyplot._tile_server import get_or_start + from polyrender._tile_server import get_or_start tiles_info = meshify( gdf, - ".polyplot", + ".polyrender", smooth=smooth, use_cache=use_cache, show_progress=show_progress, @@ -183,7 +183,7 @@ def plot( else: orbit_cap = float(max_orbit_distance) if max_orbit_distance is not None else 0.0 - widget_model = PolyFiberWidget( + widget_model = PolyRenderWidget( tile_server_url=srv.url, tiles_json_path="tiles.json", bbox=tiles_info["scene_bbox"], diff --git a/polyplot/_autoresearch_knobs.py b/polyrender/_autoresearch_knobs.py similarity index 97% rename from polyplot/_autoresearch_knobs.py rename to polyrender/_autoresearch_knobs.py index ddede73..281c9d8 100644 --- a/polyplot/_autoresearch_knobs.py +++ b/polyrender/_autoresearch_knobs.py @@ -1,6 +1,6 @@ """Internal scalar defaults for mesh export; rewritten by ``autoresearch/scripts`` drivers. -Public API is unchanged: these replace former literal defaults in :mod:`polyplot`. +Public API is unchanged: these replace former literal defaults in :mod:`polyrender`. """ from __future__ import annotations diff --git a/polyplot/_cache.py b/polyrender/_cache.py similarity index 96% rename from polyplot/_cache.py rename to polyrender/_cache.py index 5f8fa3a..d814053 100644 --- a/polyplot/_cache.py +++ b/polyrender/_cache.py @@ -8,7 +8,7 @@ from pathlib import Path # Bump when tile format / mesh pipeline changes invalidate old caches. -_CACHE_VERSION = b"polyplot_cache_v6\n" +_CACHE_VERSION = b"polyrender_cache_v6\n" # SHA256 hex directory names under the cache root. _SHA256_DIR = re.compile(r"^[0-9a-f]{64}$") @@ -38,7 +38,7 @@ def _protected_digests(root: Path) -> set[str]: """Digests under ``root`` that must not be pruned (active HTTP tile server).""" out: set[str] = set() try: - from polyplot._tile_server import get_active_tile_server + from polyrender._tile_server import get_active_tile_server srv = get_active_tile_server() if srv is None: diff --git a/polyplot/_cell_rings.py b/polyrender/_cell_rings.py similarity index 97% rename from polyplot/_cell_rings.py rename to polyrender/_cell_rings.py index 929b6f0..de1fcf6 100644 --- a/polyplot/_cell_rings.py +++ b/polyrender/_cell_rings.py @@ -7,7 +7,7 @@ from shapely.geometry import MultiPolygon as _SGMultiPolygon from shapely.geometry import Polygon as _SGPolygon -from polyplot._mesh_build import _cell_max_turning, cell_color +from polyrender._mesh_build import _cell_max_turning, cell_color def prepare_cell_rings(gdf_render, cfg: dict): diff --git a/polyplot/_mesh_build.py b/polyrender/_mesh_build.py similarity index 100% rename from polyplot/_mesh_build.py rename to polyrender/_mesh_build.py diff --git a/polyplot/_preprocess.py b/polyrender/_preprocess.py similarity index 100% rename from polyplot/_preprocess.py rename to polyrender/_preprocess.py diff --git a/polyplot/_tile_export.py b/polyrender/_tile_export.py similarity index 99% rename from polyplot/_tile_export.py rename to polyrender/_tile_export.py index 62e8c39..14286b3 100644 --- a/polyplot/_tile_export.py +++ b/polyrender/_tile_export.py @@ -24,12 +24,12 @@ from shapely.geometry import Polygon as _SGPolygon, MultiPolygon as _SGMultiPolygon import shapely as _shapely -from polyplot._mesh_build import ( +from polyrender._mesh_build import ( _adaptive_ring_targets_from_scores, _largest_polygon, build_loft_mesh_from_rings, ) -from polyplot._cell_rings import prepare_cell_rings +from polyrender._cell_rings import prepare_cell_rings # --------------------------------------------------------------------------- diff --git a/polyplot/_tile_server.py b/polyrender/_tile_server.py similarity index 100% rename from polyplot/_tile_server.py rename to polyrender/_tile_server.py diff --git a/polyplot/_widget.py b/polyrender/_widget.py similarity index 96% rename from polyplot/_widget.py rename to polyrender/_widget.py index 48da0f5..7e9ec35 100644 --- a/polyplot/_widget.py +++ b/polyrender/_widget.py @@ -6,7 +6,7 @@ import traitlets -class PolyFiberWidget(anywidget.AnyWidget): +class PolyRenderWidget(anywidget.AnyWidget): """Streaming 3D cross-section viewer backed by GLB tile files.""" _esm = pathlib.Path(__file__).parent / "widget.js" diff --git a/polyplot/widget.css b/polyrender/widget.css similarity index 100% rename from polyplot/widget.css rename to polyrender/widget.css diff --git a/polyplot/widget.js b/polyrender/widget.js similarity index 100% rename from polyplot/widget.js rename to polyrender/widget.js diff --git a/pyproject.toml b/pyproject.toml index fd1c2ae..a174c3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "polyplot" +name = "polyrender" version = "0.1.0" description = "Three Fiber-based vis widget for 2.5D polygons" requires-python = ">=3.12" @@ -39,7 +39,7 @@ requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["polyplot"] +packages = ["polyrender"] [tool.marimo.save] # Required for stable `--watch`: otherwise autosave races with external editors diff --git a/quickstart.py b/quickstart.py index 3fc1e21..1bcecbf 100644 --- a/quickstart.py +++ b/quickstart.py @@ -4,7 +4,7 @@ # "marimo>=0.22", # "geopandas>=1.0", # "pyarrow", -# "polyplot @ git+https://github.com/ckmah/polyplot.git@ec17ca181279b30b4b2befddb15d5c15d118b3b0", +# "polyrender @ git+https://github.com/ckmah/polyrender.git@ed2f47e954ecf0ac846bca10668a2754b1a7725e", # ] # /// @@ -21,11 +21,11 @@ def _(): import geopandas as gpd import marimo as mo - import polyplot as po + import polyrender as po parquet_url = os.getenv( - "POLYPLOT_PARQUET_URL", - "https://huggingface.co/datasets/ckmah/polyplot/resolve/92678be92f8e0b06fc2a32b53885c4fdf3419ee3/liver_crop_sample.parquet", + "POLYRENDER_PARQUET_URL", + "https://huggingface.co/datasets/ckmah/polyrender/resolve/92678be92f8e0b06fc2a32b53885c4fdf3419ee3/liver_crop_sample.parquet", ) return gpd, mo, parquet_url, po, textwrap @@ -50,12 +50,12 @@ def _(gpd, mo, parquet_url, po, textwrap): intro = mo.md( textwrap.dedent( f""" - # Polyplot quick start + # Polyrender quick start This notebook loads sample data from Hugging Face only, so it works in hosted MoLab sessions where only this notebook file is copied. - Set `POLYPLOT_PARQUET_URL` to any public parquet URL. + Set `POLYRENDER_PARQUET_URL` to any public parquet URL. Current source: `{parquet_url}` Tip: In `on_demand=True` mode, click a cell in the minimap to build a diff --git a/scripts/_test_232.py b/scripts/_test_232.py index 7557e62..ef8995c 100644 --- a/scripts/_test_232.py +++ b/scripts/_test_232.py @@ -1,8 +1,8 @@ """Debug exp 232: test bulk array extract timing.""" -import time, geopandas as gpd, polyplot as po -from polyplot._preprocess import preprocess_gdf -from polyplot._tile_export import auto_tile_size, compute_tile_grid, _build_tile_data -from polyplot._mesh_build import _cell_max_turning, cell_color, _collect_rings_arrays +import time, geopandas as gpd, polyrender as po +from polyrender._preprocess import preprocess_gdf +from polyrender._tile_export import auto_tile_size, compute_tile_grid, _build_tile_data +from polyrender._mesh_build import _cell_max_turning, cell_color, _collect_rings_arrays from collections import defaultdict import numpy as np diff --git a/tests/test_meshify_smoke.py b/tests/test_meshify_smoke.py index 074f165..e0b81a1 100644 --- a/tests/test_meshify_smoke.py +++ b/tests/test_meshify_smoke.py @@ -7,7 +7,7 @@ import geopandas as gpd import shapely.geometry as sg -from polyplot._api import meshify +from polyrender._api import meshify def test_meshify_writes_tiles_json_and_glbs(): diff --git a/uv.lock b/uv.lock index bff1be2..407c1a8 100644 --- a/uv.lock +++ b/uv.lock @@ -763,7 +763,7 @@ wheels = [ ] [[package]] -name = "polyplot" +name = "polyrender" version = "0.1.0" source = { editable = "." } dependencies = [ diff --git a/zensical.toml b/zensical.toml index c700e1b..06df933 100644 --- a/zensical.toml +++ b/zensical.toml @@ -11,7 +11,7 @@ # The site_name is shown in the page header and the browser window title # # Read more: https://zensical.org/docs/setup/basics/#site_name -site_name = "Polyplot" +site_name = "Polyrender" # The site_description is included in the HTML head and should contain a # meaningful description of the site content for use by search engines. @@ -27,10 +27,10 @@ site_author = "ckmah" # The site_url is the canonical URL for your site. When building online # documentation you should set this. # Read more: https://zensical.org/docs/setup/basics/#site_url -site_url = "https://ckmah.github.io/polyplot/" +site_url = "https://ckmah.github.io/polyrender/" -repo_url = "https://github.com/ckmah/polyplot" -repo_name = "ckmah/polyplot" +repo_url = "https://github.com/ckmah/polyrender" +repo_name = "ckmah/polyrender" # The copyright notice appears in the page footer and can contain an HTML # fragment. @@ -303,7 +303,7 @@ toggle.name = "Switch to light mode" # ---------------------------------------------------------------------------- [[project.extra.social]] icon = "fontawesome/brands/github" -link = "https://github.com/ckmah/polyplot" +link = "https://github.com/ckmah/polyrender" # ---------------------------------------------------------------------------- # In this section you can configure the Markdown extensions that are used when