Skip to main content

Python implementation of Karambola – Minkowski tensor morphometry of 3D structures

Project description

pykarambola

PyPI version Python versions License: BSD-3-Clause

pykarambola computes Minkowski tensors for 3D objects represented as triangulated meshes — a family of shape descriptors rooted in integral geometry that rigorously quantify size, shape, and orientation.

pykarambola — Minkowski tensor morphometry of 3D structures

Given a mesh, it returns scalar, vector, and tensor quantities including volume, surface area, integrated mean curvature, and Euler characteristic (the Minkowski functionals), as well as higher-rank tensors that capture anisotropy and preferred orientation independently of coordinate frame. pykarambola is a Python implementation of karambola, the reference C++ package for Minkowski tensor computation on 3D triangulated surfaces. Minkowski tensors are widely applicable to analyzing 3D structures in biomedical imaging, astrophysics, and materials science. For background, see Schroder-Turk et al. (2013), Mickel et al. (2013), and morphometry.org.

End-to-end pipeline: confocal nuclei → segmentation → shape clustering


Raw confocal (nuclei channel)

Segmented nuclei

PCA — scalars + β + eigvals + trace + msm

Meshes coloured by cluster

New in pykarambola

Compared to the original C++ karambola, this Python port adds:

  • OBJ, GLB, and STL parsers — read Wavefront OBJ, binary glTF (.glb), and STL (ASCII and binary) meshes directly via parse_stl_file(), in addition to the original .poly and .off formats.
  • High-level APIminkowski_tensors() accepts NumPy arrays and returns a plain dict, making it easy to integrate into pipelines without dealing with the lower-level triangulation types.
  • labels='auto' — pass labels='auto' to detect connected mesh components automatically and compute tensors for each body separately, without supplying a face-label array.
  • return_count=True — append the number of connected objects to the return value as a (results, n_objects) tuple.
  • Derived scalar quantities — each rank-2 tensor (e.g. w020) additionally yields {name}_beta (anisotropy index: ratio of smallest to largest eigenvalue magnitude), {name}_trace (matrix trace), and {name}_trace_ratio (trace divided by the corresponding Minkowski scalar, e.g. w020_trace_ratio = Tr(w020) / w000). These are pykarambola-specific extensions not present in C++ karambola; they are included in the compute='all' preset.
  • Label-image APIminkowski_tensors_from_label_image() extracts surfaces from a 3D integer label image via marching cubes and computes tensors for every label in one call.

Requirements

Optional:

  • Cython ≥ 3.0 — compiled C acceleration (pip install "pykarambola[accel]")
  • scikit-image — label-image API (pip install "pykarambola[notebooks]")
  • trimesh — GLB/glTF file support (pip install "pykarambola[glb]")
  • numpy-stl — STL file support (pip install "pykarambola[stl]")

Installation

pip install pykarambola

For optional Cython acceleration:

pip install "pykarambola[accel]"

For development (includes pytest and scikit-image):

pip install "pykarambola[dev]"

To run the example notebooks (includes scikit-image and tifffile):

pip install "pykarambola[notebooks]"

GLB/glTF support requires trimesh:

pip install "pykarambola[glb]"

You can combine extras in a single install:

pip install "pykarambola[dev,notebooks,accel]"

High-level API

From NumPy arrays

minkowski_tensors() is the main entry point. Pass vertices and faces as NumPy arrays and get back a plain dict:

import pykarambola as pk

result = pk.minkowski_tensors(
    verts,   # (V, 3) float64 array of vertex positions
    faces,   # (F, 3) int64 array of vertex indices
)

print(result["w000"])   # volume
print(result["w100"])   # surface area
print(result["w200"])   # integrated mean curvature
print(result["w300"])   # Euler characteristic
print(result["w020"])   # 3×3 Minkowski tensor
print(result["w020_eigvals"])   # eigenvalues of w020
print(result["w020_eigvecs"])   # eigenvectors of w020 (columns)

Control which quantities are computed with the compute argument:

# default: 14 standard tensors + eigensystems for rank-2 tensors
result = pk.minkowski_tensors(verts, faces, compute="standard")

# include higher-order tensors (w103, w104) and spherical Minkowski metrics
result = pk.minkowski_tensors(verts, faces, compute="all")

# compute only specific quantities
result = pk.minkowski_tensors(verts, faces, compute=["w000", "w100", "w020"])

If the mesh has boundary edges (open surface), w000 and w020 are set to NaN and a UserWarning is emitted. Non-manifold meshes also emit a UserWarning but are otherwise computed.

From a 3D label image

minkowski_tensors_from_label_image() takes a 3D integer array, runs marching cubes on each label, and returns a dict of results keyed by label value. Requires scikit-image.

import numpy as np
import pykarambola as pk

label_image = np.zeros((64, 64, 64), dtype=int)
label_image[10:40, 10:40, 10:40] = 1
label_image[40:60, 40:60, 40:60] = 2

result = pk.minkowski_tensors_from_label_image(
    label_image,
    spacing=(0.5, 0.5, 0.5),   # voxel size in physical units
    center="centroid_mesh",     # shift tensors to per-label centroid
)

print(result[1]["w000"])   # volume of label 1
print(result[2]["w100"])   # surface area of label 2

By default a 1-voxel zero border is added before running marching cubes (pad=True), so objects touching the array boundary produce closed surfaces. Pass pad=False to skip this.

The center argument controls the reference point for position-dependent tensors:

Value Behaviour
None (default for mesh API) Use the array origin (0, 0, 0)
'centroid_mesh' (default for label-image API) Shift each object to its volume-weighted centre of mass
'centroid_voxel' Use the mean voxel coordinate (label-image API only)
'reference_centroid' Reproduce the C++ karambola --reference_centroid flag
(3,) array Apply an explicit fixed shift

Multi-label meshes

Pass per-face integer labels to compute tensors for multiple bodies in a single mesh:

result = pk.minkowski_tensors(verts, faces, labels=face_labels)
# result is dict[int, dict]
print(result[1]["w000"])
print(result[2]["w000"])

Or let pykarambola detect connected components automatically:

result = pk.minkowski_tensors(verts, faces, labels="auto")
# bodies are numbered 1, 2, … by connected component
print(result[1]["w000"])

Example notebooks

Notebook What it covers
examples/demo.ipynb A hands-on tour of the mesh API: passing vertices and faces as NumPy arrays, supplying per-face labels or using labels='auto' to separate connected bodies, retrieving the object count with return_count, and computing derived scalars (_beta, _trace, _trace_ratio)
examples/segmentation_workflow.ipynb End-to-end pipeline: confocal stack → segmentation → Minkowski tensors → PCA + clustering
examples/multilabel_image_workflow.ipynb Working with 3D segmentation images: measures whole-cell morphology from a single label, compares nucleus and cell body separately using two labels, and runs per-nuclear object anisotropy analysis across three connected components from a real AllenCell hiPSC dataset
examples/minkowski_additivity.ipynb How the center parameter affects additivity of Minkowski tensors; when per-body vs. global centering matters
examples/parallel_processing.ipynb Parallel tensor computation with joblib: sequential baseline, multi-core speedup, keyword arguments, and processing mesh files in bulk

File I/O

pykarambola can read four mesh formats. The parsers return a Triangulation object that can be passed directly to minkowski_tensors().

surface = pk.parse_poly_file("my_surface.poly")   # karambola native
surface = pk.parse_off_file("my_surface.off")     # Object File Format
surface = pk.parse_obj_file("my_surface.obj")     # Wavefront OBJ  (new)
surface = pk.parse_glb_file("my_surface.glb")     # binary glTF    (new, requires trimesh)
surface = pk.parse_stl_file("my_surface.stl")     # STL ASCII/binary (new, requires numpy-stl)

result = pk.minkowski_tensors(surface)
Extension Description
.poly karambola native format
.off Object File Format
.obj Wavefront OBJ
.glb GL Transmission Format (binary glTF) — requires trimesh
.stl STereoLithography (ASCII and binary) — requires numpy-stl

Command-line interface

python -m pykarambola [options] <surface_file>

Supported input formats: .poly, .off, .obj, .glb, .stl. Run python -m pykarambola --help for the full list of options.

Computed quantities

All quantities below are returned by compute='standard' unless noted (compute='all').

Normalization convention. pykarambola follows the karambola/Hadwiger convention in which each Minkowski functional carries a factor of 1/3: w100 = surface area / 3, w200 = integrated mean curvature / 3, w300 = 2π χ / 3. Recover physical quantities as A = 3·w100, M = 3·w200, χ = 3·w300 / (2π). For full mathematical definitions, discrete formulas, and normalization derivations see docs/minkowski_tensors.md.

Name Type Description
w000 scalar Volume
w100 scalar Surface area / 3
w200 scalar Integrated mean curvature / 3
w300 scalar 2π × Euler characteristic / 3
w010 vector Minkowski vector (volume)
w110 vector Minkowski vector (surface)
w210 vector Minkowski vector (curvature)
w310 vector Minkowski vector (topology)
w020 rank-2 tensor Minkowski tensor (volume)
w120 rank-2 tensor Minkowski tensor (surface)
w220 rank-2 tensor Minkowski tensor (curvature)
w320 rank-2 tensor Minkowski tensor (topology)
w102 rank-2 tensor Minkowski tensor (surface, normal-normal)
w202 rank-2 tensor Minkowski tensor (curvature, normal-normal)
w103 rank-3 tensor Higher-order tensor (compute='all')
w104 rank-4 tensor Higher-order tensor (compute='all')
msm_ql, msm_wl arrays Minkowski structure metrics (spherical, compute='all')
{name}_beta scalar Anisotropy index: min|λ| / max|λ| for each rank-2 tensor (compute='all')
{name}_trace scalar Trace of each rank-2 tensor matrix (compute='all')
{name}_trace_ratio scalar Trace divided by corresponding Minkowski scalar, e.g. Tr(w020)/w000 (wX20 family only; compute='all')

Rank-2 tensors additionally yield {name}_eigvals and {name}_eigvecs entries.

FAQ

My mesh is not water-tight. What should I do?

pykarambola will still run on open (non-water-tight) meshes and will emit a UserWarning listing the affected labels. Volume-dependent quantities (w000, w020) are set to NaN for open labels because the divergence theorem requires a closed surface to define volume unambiguously. All other quantities — surface area (w100), curvature integrals (w200, w300), and their associated vectors and tensors — remain valid and are computed normally.

If you need volume, the recommended fix is to close the surface before calling pykarambola. Common tools for this are PyMeshFix (pymeshfix.MeshFix(verts, faces).repair()) and Open3D (mesh.fill_holes()). Alternatively, if your mesh comes from a 3D label image, use minkowski_tensors_from_label_image directly — it always produces closed surfaces via marching cubes and automatically pads the image at boundaries to prevent open surfaces.

I have point cloud data. Can I use pykarambola?

Not directly — pykarambola requires a triangulated surface mesh (vertex array + face array), not raw point positions. You first need to reconstruct a surface from your point cloud. Open3D provides two common approaches: Poisson surface reconstruction (o3d.geometry.TriangleMesh.create_from_point_cloud_poisson) for smooth, water-tight surfaces, and ball-pivoting (create_from_point_cloud_ball_pivoting) for locally faithful but potentially open surfaces. Once you have a mesh, pass its vertex and face arrays to minkowski_tensors(verts, faces) directly.

Citation

If you use pykarambola in your work, please cite pykarambola and Schröder-Turk group's publication on Minkowski tensors:

Ishihara, K., & Khurana, Y. pykarambola: Minkowski tensor morphometry of 3D structures (v0.5.0). https://doi.org/10.5281/zenodo.20418801

Schröder-Turk, G. E., Mickel, W., Kapfer, S. C., Schaller, F. M., Breidenbach, B., Hug, D., & Mecke, K. Minkowski tensors of anisotropic spatial structure. New Journal of Physics, 15, 083028 (2013). https://doi.org/10.1088/1367-2630/15/8/083028

Contributing

See CONTRIBUTING.md for development setup, Git workflow, versioning, and release instructions. See CHANGELOG.md for a history of changes between versions.

License

pykarambola is released under the BSD 3-Clause License.

The authors of karambola — Schaller, Kapfer, and Schröder-Turk — kindly agreed to pykarambola being distributed under the BSD 3-Clause License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pykarambola-0.5.1.tar.gz (66.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pykarambola-0.5.1-py3-none-any.whl (49.3 kB view details)

Uploaded Python 3

File details

Details for the file pykarambola-0.5.1.tar.gz.

File metadata

  • Download URL: pykarambola-0.5.1.tar.gz
  • Upload date:
  • Size: 66.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.9

File hashes

Hashes for pykarambola-0.5.1.tar.gz
Algorithm Hash digest
SHA256 28963ec5e3fa78c26ab394263ecc1ff11be6635f77d51b9199f48269c7d15711
MD5 c4ff87e31e0b20f2a418b47f03030f60
BLAKE2b-256 2f2fce908104ee3c1a8c834ef3e1366fc3c85266d2d17e489bd6256a2dc4a7cf

See more details on using hashes here.

File details

Details for the file pykarambola-0.5.1-py3-none-any.whl.

File metadata

  • Download URL: pykarambola-0.5.1-py3-none-any.whl
  • Upload date:
  • Size: 49.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.9

File hashes

Hashes for pykarambola-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 be999fef723ae2eb25f646d604d38f2a3a29590b933683e045afdda52a53b851
MD5 140cf9d9dae82d1e27675b4b0aaaed8d
BLAKE2b-256 de452dcfe27df8585e2ea71a3eeba85ca8439dc254770accff70ce142075e113

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page