pykarambola 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. pykarambola-0.5.1/LICENSE +28 -0
  2. pykarambola-0.5.1/MANIFEST.in +1 -0
  3. pykarambola-0.5.1/PKG-INFO +374 -0
  4. pykarambola-0.5.1/README.md +301 -0
  5. pykarambola-0.5.1/pykarambola/__init__.py +34 -0
  6. pykarambola-0.5.1/pykarambola/__main__.py +9 -0
  7. pykarambola-0.5.1/pykarambola/_accel.pyx +115 -0
  8. pykarambola-0.5.1/pykarambola/api.py +919 -0
  9. pykarambola-0.5.1/pykarambola/cli.py +312 -0
  10. pykarambola-0.5.1/pykarambola/eigensystem.py +77 -0
  11. pykarambola-0.5.1/pykarambola/io_glb.py +88 -0
  12. pykarambola-0.5.1/pykarambola/io_obj.py +95 -0
  13. pykarambola-0.5.1/pykarambola/io_off.py +109 -0
  14. pykarambola-0.5.1/pykarambola/io_poly.py +189 -0
  15. pykarambola-0.5.1/pykarambola/io_stl.py +85 -0
  16. pykarambola-0.5.1/pykarambola/minkowski.py +578 -0
  17. pykarambola-0.5.1/pykarambola/output.py +317 -0
  18. pykarambola-0.5.1/pykarambola/results.py +116 -0
  19. pykarambola-0.5.1/pykarambola/spherical.py +209 -0
  20. pykarambola-0.5.1/pykarambola/surface.py +124 -0
  21. pykarambola-0.5.1/pykarambola/tensor.py +123 -0
  22. pykarambola-0.5.1/pykarambola/triangulation.py +324 -0
  23. pykarambola-0.5.1/pykarambola.egg-info/PKG-INFO +374 -0
  24. pykarambola-0.5.1/pykarambola.egg-info/SOURCES.txt +35 -0
  25. pykarambola-0.5.1/pykarambola.egg-info/dependency_links.txt +1 -0
  26. pykarambola-0.5.1/pykarambola.egg-info/requires.txt +25 -0
  27. pykarambola-0.5.1/pykarambola.egg-info/top_level.txt +1 -0
  28. pykarambola-0.5.1/pyproject.toml +49 -0
  29. pykarambola-0.5.1/setup.cfg +4 -0
  30. pykarambola-0.5.1/setup.py +15 -0
  31. pykarambola-0.5.1/tests/test_accel.py +178 -0
  32. pykarambola-0.5.1/tests/test_api.py +938 -0
  33. pykarambola-0.5.1/tests/test_box.py +255 -0
  34. pykarambola-0.5.1/tests/test_mesh_quality.py +591 -0
  35. pykarambola-0.5.1/tests/test_nonconvex.py +183 -0
  36. pykarambola-0.5.1/tests/test_readers.py +291 -0
  37. pykarambola-0.5.1/tests/test_wigner3j.py +82 -0
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026 Keisuke Ishihara, Yajushi Khurana
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1 @@
1
+ include pykarambola/_accel.pyx
@@ -0,0 +1,374 @@
1
+ Metadata-Version: 2.4
2
+ Name: pykarambola
3
+ Version: 0.5.1
4
+ Summary: Python implementation of Karambola – Minkowski tensor morphometry of 3D structures
5
+ License: BSD 3-Clause License
6
+
7
+ Copyright (c) 2026 Keisuke Ishihara, Yajushi Khurana
8
+
9
+ Redistribution and use in source and binary forms, with or without
10
+ modification, are permitted provided that the following conditions are met:
11
+
12
+ 1. Redistributions of source code must retain the above copyright notice, this
13
+ list of conditions and the following disclaimer.
14
+
15
+ 2. Redistributions in binary form must reproduce the above copyright notice,
16
+ this list of conditions and the following disclaimer in the documentation
17
+ and/or other materials provided with the distribution.
18
+
19
+ 3. Neither the name of the copyright holder nor the names of its
20
+ contributors may be used to endorse or promote products derived from
21
+ this software without specific prior written permission.
22
+
23
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
+
34
+ Project-URL: Homepage, https://github.com/Ishihara-SynthMorph/pykarambola
35
+ Project-URL: Documentation, https://github.com/Ishihara-SynthMorph/pykarambola#readme
36
+ Project-URL: Bug Tracker, https://github.com/Ishihara-SynthMorph/pykarambola/issues
37
+ Keywords: minkowski,tensors,bioimage,morphometry,3d-shape-analysis,mesh,geometry,surface-analysis,computational-geometry,shape-descriptor,materials-science,minkowski-functionals,triangulated-surface,image-analysis
38
+ Classifier: License :: OSI Approved :: BSD License
39
+ Classifier: Programming Language :: Python :: 3
40
+ Classifier: Programming Language :: Python :: 3.9
41
+ Classifier: Programming Language :: Python :: 3.10
42
+ Classifier: Programming Language :: Python :: 3.11
43
+ Classifier: Programming Language :: Python :: 3.12
44
+ Classifier: Programming Language :: Python :: 3.13
45
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
46
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
47
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
48
+ Classifier: Topic :: Scientific/Engineering :: Physics
49
+ Requires-Python: >=3.9
50
+ Description-Content-Type: text/markdown
51
+ License-File: LICENSE
52
+ Requires-Dist: numpy>=1.22
53
+ Requires-Dist: scipy>=1.8
54
+ Provides-Extra: dev
55
+ Requires-Dist: pytest; extra == "dev"
56
+ Requires-Dist: scikit-image>=0.19; extra == "dev"
57
+ Provides-Extra: accel
58
+ Requires-Dist: cython>=3.0; extra == "accel"
59
+ Provides-Extra: glb
60
+ Requires-Dist: trimesh; extra == "glb"
61
+ Provides-Extra: stl
62
+ Requires-Dist: numpy-stl; extra == "stl"
63
+ Provides-Extra: notebooks
64
+ Requires-Dist: scikit-image>=0.19; extra == "notebooks"
65
+ Requires-Dist: matplotlib; extra == "notebooks"
66
+ Requires-Dist: seaborn; extra == "notebooks"
67
+ Requires-Dist: pandas; extra == "notebooks"
68
+ Requires-Dist: medmnist; extra == "notebooks"
69
+ Requires-Dist: tifffile; extra == "notebooks"
70
+ Requires-Dist: scikit-learn; extra == "notebooks"
71
+ Requires-Dist: pooch; extra == "notebooks"
72
+ Dynamic: license-file
73
+
74
+ # pykarambola
75
+ <!-- CI badge disabled: GitHub Actions disabled at org level -->
76
+ <!-- [![CI](https://github.com/Ishihara-SynthMorph/pykarambola/actions/workflows/ci.yml/badge.svg)](https://github.com/Ishihara-SynthMorph/pykarambola/actions/workflows/ci.yml) -->
77
+ [![PyPI version](https://img.shields.io/pypi/v/pykarambola)](https://pypi.org/project/pykarambola/)
78
+ [![Python versions](https://img.shields.io/pypi/pyversions/pykarambola)](https://pypi.org/project/pykarambola/)
79
+ [![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
80
+
81
+ **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.
82
+
83
+ <p align="center">
84
+ <img src="assets/banner.png" alt="pykarambola — Minkowski tensor morphometry of 3D structures" width="75%"/>
85
+ </p>
86
+
87
+ 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.
88
+ pykarambola is a Python implementation of [karambola](https://github.com/morphometry/karambola), the reference C++ package for Minkowski tensor computation on 3D triangulated surfaces.
89
+ Minkowski tensors are widely applicable to analyzing 3D structures in biomedical imaging, astrophysics, and materials science.
90
+ For background, see [Schroder-Turk et al. (2013)](https://doi.org/10.1088/1367-2630/15/8/083028), [Mickel et al. (2013)](https://doi.org/10.1063/1.4774084), and [morphometry.org](https://morphometry.org/).
91
+
92
+ ### End-to-end pipeline: confocal nuclei → segmentation → shape clustering
93
+
94
+ <table align="center">
95
+ <tr>
96
+ <td align="center"><img src="assets/demo_raw.png" width="220"/><br/><sub>Raw confocal (nuclei channel)</sub></td>
97
+ <td align="center"><img src="assets/demo_segmented.png" width="220"/><br/><sub>Segmented nuclei</sub></td>
98
+ </tr>
99
+ <tr>
100
+ <td align="center"><img src="assets/demo_pca_full.png" width="220"/><br/><sub>PCA — scalars + β + eigvals + trace + msm</sub></td>
101
+ <td align="center"><img src="assets/demo_mesh_full.png" width="220"/><br/><sub>Meshes coloured by cluster</sub></td>
102
+ </tr>
103
+ </table>
104
+
105
+ ## New in pykarambola
106
+
107
+ Compared to the original C++ karambola, this Python port adds:
108
+
109
+ - **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.
110
+ - **High-level API** — `minkowski_tensors()` accepts NumPy arrays and returns a plain dict, making it easy to integrate into pipelines without dealing with the lower-level triangulation types.
111
+ - **`labels='auto'`** — pass `labels='auto'` to detect connected mesh components automatically and compute tensors for each body separately, without supplying a face-label array.
112
+ - **`return_count=True`** — append the number of connected objects to the return value as a `(results, n_objects)` tuple.
113
+ - **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`).
114
+ These are pykarambola-specific extensions not present in C++ karambola; they are included in the `compute='all'` preset.
115
+ - **Label-image API** — `minkowski_tensors_from_label_image()` extracts surfaces from a 3D integer label image via marching cubes and computes tensors for every label in one call.
116
+
117
+ ## Requirements
118
+
119
+ - Python ≥ 3.9
120
+ - [NumPy](https://numpy.org/)
121
+ - [SciPy](https://scipy.org/)
122
+
123
+ **Optional:**
124
+ - [Cython](https://cython.org/) ≥ 3.0 — compiled C acceleration (`pip install "pykarambola[accel]"`)
125
+ - [scikit-image](https://scikit-image.org/) — label-image API (`pip install "pykarambola[notebooks]"`)
126
+ - [trimesh](https://trimesh.org/) — GLB/glTF file support (`pip install "pykarambola[glb]"`)
127
+ - [numpy-stl](https://github.com/WoLpH/numpy-stl) — STL file support (`pip install "pykarambola[stl]"`)
128
+
129
+ ## Installation
130
+
131
+ ```bash
132
+ pip install pykarambola
133
+ ```
134
+
135
+ For optional Cython acceleration:
136
+
137
+ ```bash
138
+ pip install "pykarambola[accel]"
139
+ ```
140
+
141
+ For development (includes pytest and scikit-image):
142
+
143
+ ```bash
144
+ pip install "pykarambola[dev]"
145
+ ```
146
+
147
+ To run the example notebooks (includes scikit-image and tifffile):
148
+
149
+ ```bash
150
+ pip install "pykarambola[notebooks]"
151
+ ```
152
+
153
+ GLB/glTF support requires [trimesh](https://trimesh.org/):
154
+
155
+ ```bash
156
+ pip install "pykarambola[glb]"
157
+ ```
158
+
159
+ You can combine extras in a single install:
160
+
161
+ ```bash
162
+ pip install "pykarambola[dev,notebooks,accel]"
163
+ ```
164
+
165
+ ## High-level API
166
+
167
+ ### From NumPy arrays
168
+
169
+ `minkowski_tensors()` is the main entry point. Pass vertices and faces as NumPy arrays and get back a plain dict:
170
+
171
+ ```python
172
+ import pykarambola as pk
173
+
174
+ result = pk.minkowski_tensors(
175
+ verts, # (V, 3) float64 array of vertex positions
176
+ faces, # (F, 3) int64 array of vertex indices
177
+ )
178
+
179
+ print(result["w000"]) # volume
180
+ print(result["w100"]) # surface area
181
+ print(result["w200"]) # integrated mean curvature
182
+ print(result["w300"]) # Euler characteristic
183
+ print(result["w020"]) # 3×3 Minkowski tensor
184
+ print(result["w020_eigvals"]) # eigenvalues of w020
185
+ print(result["w020_eigvecs"]) # eigenvectors of w020 (columns)
186
+ ```
187
+
188
+ Control which quantities are computed with the `compute` argument:
189
+
190
+ ```python
191
+ # default: 14 standard tensors + eigensystems for rank-2 tensors
192
+ result = pk.minkowski_tensors(verts, faces, compute="standard")
193
+
194
+ # include higher-order tensors (w103, w104) and spherical Minkowski metrics
195
+ result = pk.minkowski_tensors(verts, faces, compute="all")
196
+
197
+ # compute only specific quantities
198
+ result = pk.minkowski_tensors(verts, faces, compute=["w000", "w100", "w020"])
199
+ ```
200
+
201
+ 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.
202
+
203
+ ### From a 3D label image
204
+
205
+ `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](https://scikit-image.org/).
206
+
207
+ ```python
208
+ import numpy as np
209
+ import pykarambola as pk
210
+
211
+ label_image = np.zeros((64, 64, 64), dtype=int)
212
+ label_image[10:40, 10:40, 10:40] = 1
213
+ label_image[40:60, 40:60, 40:60] = 2
214
+
215
+ result = pk.minkowski_tensors_from_label_image(
216
+ label_image,
217
+ spacing=(0.5, 0.5, 0.5), # voxel size in physical units
218
+ center="centroid_mesh", # shift tensors to per-label centroid
219
+ )
220
+
221
+ print(result[1]["w000"]) # volume of label 1
222
+ print(result[2]["w100"]) # surface area of label 2
223
+ ```
224
+
225
+ 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.
226
+
227
+ The `center` argument controls the reference point for position-dependent tensors:
228
+
229
+ | Value | Behaviour |
230
+ |-------|-----------|
231
+ | `None` (default for mesh API) | Use the array origin `(0, 0, 0)` |
232
+ | `'centroid_mesh'` (default for label-image API) | Shift each object to its volume-weighted centre of mass |
233
+ | `'centroid_voxel'` | Use the mean voxel coordinate (label-image API only) |
234
+ | `'reference_centroid'` | Reproduce the C++ karambola `--reference_centroid` flag |
235
+ | `(3,)` array | Apply an explicit fixed shift |
236
+
237
+ ### Multi-label meshes
238
+
239
+ Pass per-face integer labels to compute tensors for multiple bodies in a single mesh:
240
+
241
+ ```python
242
+ result = pk.minkowski_tensors(verts, faces, labels=face_labels)
243
+ # result is dict[int, dict]
244
+ print(result[1]["w000"])
245
+ print(result[2]["w000"])
246
+ ```
247
+
248
+ Or let pykarambola detect connected components automatically:
249
+
250
+ ```python
251
+ result = pk.minkowski_tensors(verts, faces, labels="auto")
252
+ # bodies are numbered 1, 2, … by connected component
253
+ print(result[1]["w000"])
254
+ ```
255
+
256
+ ## Example notebooks
257
+
258
+ | Notebook | What it covers |
259
+ |----------|----------------|
260
+ | [`examples/demo.ipynb`](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`) |
261
+ | [`examples/segmentation_workflow.ipynb`](examples/segmentation_workflow.ipynb) | End-to-end pipeline: confocal stack → segmentation → Minkowski tensors → PCA + clustering |
262
+ | [`examples/multilabel_image_workflow.ipynb`](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 |
263
+ | [`examples/minkowski_additivity.ipynb`](examples/minkowski_additivity.ipynb) | How the `center` parameter affects additivity of Minkowski tensors; when per-body vs. global centering matters |
264
+ | [`examples/parallel_processing.ipynb`](examples/parallel_processing.ipynb) | Parallel tensor computation with `joblib`: sequential baseline, multi-core speedup, keyword arguments, and processing mesh files in bulk |
265
+
266
+ ## File I/O
267
+
268
+ pykarambola can read four mesh formats. The parsers return a `Triangulation` object that can be passed directly to `minkowski_tensors()`.
269
+
270
+ ```python
271
+ surface = pk.parse_poly_file("my_surface.poly") # karambola native
272
+ surface = pk.parse_off_file("my_surface.off") # Object File Format
273
+ surface = pk.parse_obj_file("my_surface.obj") # Wavefront OBJ (new)
274
+ surface = pk.parse_glb_file("my_surface.glb") # binary glTF (new, requires trimesh)
275
+ surface = pk.parse_stl_file("my_surface.stl") # STL ASCII/binary (new, requires numpy-stl)
276
+
277
+ result = pk.minkowski_tensors(surface)
278
+ ```
279
+
280
+ | Extension | Description |
281
+ |-----------|-------------|
282
+ | `.poly` | karambola native format |
283
+ | `.off` | Object File Format |
284
+ | `.obj` | Wavefront OBJ |
285
+ | `.glb` | GL Transmission Format (binary glTF) — requires `trimesh` |
286
+ | `.stl` | STereoLithography (ASCII and binary) — requires `numpy-stl` |
287
+
288
+ ## Command-line interface
289
+
290
+ ```
291
+ python -m pykarambola [options] <surface_file>
292
+ ```
293
+
294
+ Supported input formats: `.poly`, `.off`, `.obj`, `.glb`, `.stl`.
295
+ Run `python -m pykarambola --help` for the full list of options.
296
+
297
+ ## Computed quantities
298
+
299
+ All quantities below are returned by `compute='standard'` unless noted `(compute='all')`.
300
+
301
+ > **Normalization convention.** pykarambola follows the karambola/Hadwiger convention in which
302
+ > each Minkowski functional carries a factor of 1/3: `w100` = surface area / 3,
303
+ > `w200` = integrated mean curvature / 3, `w300` = 2π χ / 3.
304
+ > Recover physical quantities as `A = 3·w100`, `M = 3·w200`, `χ = 3·w300 / (2π)`.
305
+ > For full mathematical definitions, discrete formulas, and normalization derivations see
306
+ > [`docs/minkowski_tensors.md`](docs/minkowski_tensors.md).
307
+
308
+ | Name | Type | Description |
309
+ |------|------|-------------|
310
+ | `w000` | scalar | Volume |
311
+ | `w100` | scalar | Surface area / 3 |
312
+ | `w200` | scalar | Integrated mean curvature / 3 |
313
+ | `w300` | scalar | 2π × Euler characteristic / 3 |
314
+ | `w010` | vector | Minkowski vector (volume) |
315
+ | `w110` | vector | Minkowski vector (surface) |
316
+ | `w210` | vector | Minkowski vector (curvature) |
317
+ | `w310` | vector | Minkowski vector (topology) |
318
+ | `w020` | rank-2 tensor | Minkowski tensor (volume) |
319
+ | `w120` | rank-2 tensor | Minkowski tensor (surface) |
320
+ | `w220` | rank-2 tensor | Minkowski tensor (curvature) |
321
+ | `w320` | rank-2 tensor | Minkowski tensor (topology) |
322
+ | `w102` | rank-2 tensor | Minkowski tensor (surface, normal-normal) |
323
+ | `w202` | rank-2 tensor | Minkowski tensor (curvature, normal-normal) |
324
+ | `w103` | rank-3 tensor | Higher-order tensor (`compute='all'`) |
325
+ | `w104` | rank-4 tensor | Higher-order tensor (`compute='all'`) |
326
+ | `msm_ql`, `msm_wl` | arrays | Minkowski structure metrics (spherical, `compute='all'`) |
327
+ | `{name}_beta` | scalar | Anisotropy index: min\|λ\| / max\|λ\| for each rank-2 tensor (`compute='all'`) |
328
+ | `{name}_trace` | scalar | Trace of each rank-2 tensor matrix (`compute='all'`) |
329
+ | `{name}_trace_ratio` | scalar | Trace divided by corresponding Minkowski scalar, e.g. `Tr(w020)/w000` (wX20 family only; `compute='all'`) |
330
+
331
+ Rank-2 tensors additionally yield `{name}_eigvals` and `{name}_eigvecs` entries.
332
+
333
+ ## FAQ
334
+
335
+ **My mesh is not water-tight. What should I do?**
336
+
337
+ pykarambola will still run on open (non-water-tight) meshes and will emit a `UserWarning` listing the affected labels.
338
+ Volume-dependent quantities (`w000`, `w020`) are set to `NaN` for open labels because the divergence theorem requires a closed surface to define volume unambiguously.
339
+ All other quantities — surface area (`w100`), curvature integrals (`w200`, `w300`), and their associated vectors and tensors — remain valid and are computed normally.
340
+
341
+ If you need volume, the recommended fix is to close the surface before calling pykarambola.
342
+ Common tools for this are [PyMeshFix](https://github.com/pyvista/pymeshfix) (`pymeshfix.MeshFix(verts, faces).repair()`) and [Open3D](http://www.open3d.org/) (`mesh.fill_holes()`).
343
+ 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.
344
+
345
+ **I have point cloud data. Can I use pykarambola?**
346
+
347
+ Not directly — pykarambola requires a triangulated surface mesh (vertex array + face array), not raw point positions.
348
+ You first need to reconstruct a surface from your point cloud.
349
+ [Open3D](http://www.open3d.org/) 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.
350
+ Once you have a mesh, pass its vertex and face arrays to `minkowski_tensors(verts, faces)` directly.
351
+
352
+ ## Citation
353
+
354
+ If you use pykarambola in your work, please cite pykarambola and Schröder-Turk group's publication on Minkowski tensors:
355
+
356
+ > Ishihara, K., & Khurana, Y.
357
+ > *pykarambola: Minkowski tensor morphometry of 3D structures* (v0.5.0).
358
+ > https://doi.org/10.5281/zenodo.20418801
359
+
360
+ > Schröder-Turk, G. E., Mickel, W., Kapfer, S. C., Schaller, F. M., Breidenbach, B., Hug, D., & Mecke, K.
361
+ > *Minkowski tensors of anisotropic spatial structure.*
362
+ > *New Journal of Physics*, 15, 083028 (2013).
363
+ > https://doi.org/10.1088/1367-2630/15/8/083028
364
+
365
+ ## Contributing
366
+
367
+ See [`CONTRIBUTING.md`](CONTRIBUTING.md) for development setup, Git workflow, versioning, and release instructions.
368
+ See [`CHANGELOG.md`](CHANGELOG.md) for a history of changes between versions.
369
+
370
+ ## License
371
+
372
+ pykarambola is released under the [BSD 3-Clause License](LICENSE).
373
+
374
+ The authors of [karambola](https://github.com/morphometry/karambola) — Schaller, Kapfer, and Schröder-Turk — kindly agreed to pykarambola being distributed under the BSD 3-Clause License.