ply3d 0.1.0__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.
- ply3d-0.1.0/.gitignore +106 -0
- ply3d-0.1.0/.gitlab-ci.yml +70 -0
- ply3d-0.1.0/PKG-INFO +65 -0
- ply3d-0.1.0/README.md +52 -0
- ply3d-0.1.0/docs/gen_pages.py +34 -0
- ply3d-0.1.0/docs/import.md +63 -0
- ply3d-0.1.0/docs/index.md +110 -0
- ply3d-0.1.0/docs/io.md +50 -0
- ply3d-0.1.0/docs/mesh.md +104 -0
- ply3d-0.1.0/docs/visualization.md +163 -0
- ply3d-0.1.0/environment.yml +16 -0
- ply3d-0.1.0/example_data/3Dhypocotyl.init +6315 -0
- ply3d-0.1.0/example_data/3Dhypocotyl.ply +4307 -0
- ply3d-0.1.0/example_data/CylinderCells.ply +1331 -0
- ply3d-0.1.0/example_data/PavementCellsMerged.ply +144566 -0
- ply3d-0.1.0/example_data/PavementCellsSeperated.ply +88131 -0
- ply3d-0.1.0/example_data/RadicleSmall.ply +649524 -0
- ply3d-0.1.0/example_data/dome3.init +0 -0
- ply3d-0.1.0/example_data/dome3D.ply +39821 -0
- ply3d-0.1.0/example_data/dome3D_test.ply +23 -0
- ply3d-0.1.0/example_data/hexagonal_prisms.ply +200 -0
- ply3d-0.1.0/example_data/hexagonal_prisms_triangulated.ply +608 -0
- ply3d-0.1.0/example_data/meristemE.init +3190 -0
- ply3d-0.1.0/example_data/meristemE.neigh +3191 -0
- ply3d-0.1.0/example_data/segmentation_topomesh.ply +8315 -0
- ply3d-0.1.0/example_data/teapot.obj +2090 -0
- ply3d-0.1.0/example_data/template2dFine.init +4329 -0
- ply3d-0.1.0/example_data/yr01_t081_seg_rot_simple_draco_topomesh.ply +8446 -0
- ply3d-0.1.0/example_data/yr01_t081_seg_rot_simple_draco_topomesh_new.ply +8446 -0
- ply3d-0.1.0/mkdocs.yml +67 -0
- ply3d-0.1.0/pyproject.toml +34 -0
- ply3d-0.1.0/setup.cfg +4 -0
- ply3d-0.1.0/src/ply3d/__init__.py +0 -0
- ply3d-0.1.0/src/ply3d/classes.py +123 -0
- ply3d-0.1.0/src/ply3d/cli/__init__.py +0 -0
- ply3d-0.1.0/src/ply3d/cli/converter_py.py +57 -0
- ply3d-0.1.0/src/ply3d/deprecated/__init__.py +0 -0
- ply3d-0.1.0/src/ply3d/deprecated/ply_parser_dict.py +326 -0
- ply3d-0.1.0/src/ply3d/deprecated/test.py +20 -0
- ply3d-0.1.0/src/ply3d/deprecated/test_ply_parser.py +21 -0
- ply3d-0.1.0/src/ply3d/deprecated/test_ply_parser_binary.py +22 -0
- ply3d-0.1.0/src/ply3d/deprecated/test_ply_parser_read.py +19 -0
- ply3d-0.1.0/src/ply3d/init_parser.py +158 -0
- ply3d-0.1.0/src/ply3d/init_plotter.py +41 -0
- ply3d-0.1.0/src/ply3d/mesh.py +534 -0
- ply3d-0.1.0/src/ply3d/meshio_import.py +368 -0
- ply3d-0.1.0/src/ply3d/ply_parser.py +690 -0
- ply3d-0.1.0/src/ply3d/ply_parser_string.py +134 -0
- ply3d-0.1.0/src/ply3d/test_output.ply +13 -0
- ply3d-0.1.0/src/ply3d/test_output_converted.ply +13 -0
- ply3d-0.1.0/src/ply3d/vtk_export.py +352 -0
- ply3d-0.1.0/src/ply3d/vtk_view.py +118 -0
- ply3d-0.1.0/src/ply3d.egg-info/PKG-INFO +65 -0
- ply3d-0.1.0/src/ply3d.egg-info/SOURCES.txt +58 -0
- ply3d-0.1.0/src/ply3d.egg-info/dependency_links.txt +1 -0
- ply3d-0.1.0/src/ply3d.egg-info/requires.txt +4 -0
- ply3d-0.1.0/src/ply3d.egg-info/top_level.txt +1 -0
- ply3d-0.1.0/test/test_meshio_import.py +22 -0
- ply3d-0.1.0/test/test_ply_mesh.py +30 -0
- ply3d-0.1.0/test/test_vtk_export.py +38 -0
ply3d-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
.idea/
|
|
2
|
+
*.swp
|
|
3
|
+
|
|
4
|
+
# Byte-compiled / optimized / DLL files
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
*$py.class
|
|
8
|
+
|
|
9
|
+
# C extensions
|
|
10
|
+
*.so
|
|
11
|
+
|
|
12
|
+
# Distribution / packaging
|
|
13
|
+
.Python
|
|
14
|
+
build/
|
|
15
|
+
develop-eggs/
|
|
16
|
+
dist/
|
|
17
|
+
downloads/
|
|
18
|
+
eggs/
|
|
19
|
+
.eggs/
|
|
20
|
+
lib/
|
|
21
|
+
lib64/
|
|
22
|
+
parts/
|
|
23
|
+
sdist/
|
|
24
|
+
var/
|
|
25
|
+
wheels/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
.installed.cfg
|
|
28
|
+
*.egg
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
# Usually these files are written by a python script from a template
|
|
32
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
33
|
+
*.manifest
|
|
34
|
+
*.spec
|
|
35
|
+
|
|
36
|
+
# Installer logs
|
|
37
|
+
pip-log.txt
|
|
38
|
+
pip-delete-this-directory.txt
|
|
39
|
+
|
|
40
|
+
# Unit test / coverage reports
|
|
41
|
+
htmlcov/
|
|
42
|
+
.tox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
.hypothesis/
|
|
50
|
+
|
|
51
|
+
# Translations
|
|
52
|
+
*.mo
|
|
53
|
+
*.pot
|
|
54
|
+
|
|
55
|
+
# Django stuff:
|
|
56
|
+
*.log
|
|
57
|
+
local_settings.py
|
|
58
|
+
|
|
59
|
+
# Flask stuff:
|
|
60
|
+
instance/
|
|
61
|
+
.webassets-cache
|
|
62
|
+
|
|
63
|
+
# Scrapy stuff:
|
|
64
|
+
.scrapy
|
|
65
|
+
|
|
66
|
+
# Sphinx documentation
|
|
67
|
+
docs/_build/
|
|
68
|
+
|
|
69
|
+
# PyBuilder
|
|
70
|
+
target/
|
|
71
|
+
|
|
72
|
+
# Jupyter Notebook
|
|
73
|
+
.ipynb_checkpoints
|
|
74
|
+
.virtual_documents
|
|
75
|
+
|
|
76
|
+
# pyenv
|
|
77
|
+
.python-version
|
|
78
|
+
|
|
79
|
+
# celery beat schedule file
|
|
80
|
+
celerybeat-schedule
|
|
81
|
+
|
|
82
|
+
# SageMath parsed files
|
|
83
|
+
*.sage.py
|
|
84
|
+
|
|
85
|
+
# Environments
|
|
86
|
+
.env
|
|
87
|
+
.venv
|
|
88
|
+
env/
|
|
89
|
+
venv/
|
|
90
|
+
ENV/
|
|
91
|
+
|
|
92
|
+
# Spyder project settings
|
|
93
|
+
.spyderproject
|
|
94
|
+
.spyproject
|
|
95
|
+
|
|
96
|
+
# Rope project settings
|
|
97
|
+
.ropeproject
|
|
98
|
+
|
|
99
|
+
# mkdocs documentation
|
|
100
|
+
/site
|
|
101
|
+
|
|
102
|
+
# mypy
|
|
103
|
+
.mypy_cache/
|
|
104
|
+
|
|
105
|
+
# macos
|
|
106
|
+
.DS_Store
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
image: continuumio/miniconda3:latest
|
|
2
|
+
cache:
|
|
3
|
+
paths:
|
|
4
|
+
- .pip-cache/
|
|
5
|
+
before_script:
|
|
6
|
+
- python --version
|
|
7
|
+
- pip install --upgrade pip
|
|
8
|
+
- pip install build twine
|
|
9
|
+
|
|
10
|
+
stages:
|
|
11
|
+
- build
|
|
12
|
+
- test
|
|
13
|
+
- deploy
|
|
14
|
+
|
|
15
|
+
variables:
|
|
16
|
+
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
|
|
17
|
+
|
|
18
|
+
build:
|
|
19
|
+
stage: build
|
|
20
|
+
script:
|
|
21
|
+
- python -m build
|
|
22
|
+
artifacts:
|
|
23
|
+
paths:
|
|
24
|
+
- dist/
|
|
25
|
+
rules:
|
|
26
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
27
|
+
when: manual
|
|
28
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
29
|
+
|
|
30
|
+
test:
|
|
31
|
+
stage: test
|
|
32
|
+
script:
|
|
33
|
+
- pip install dist/*.whl
|
|
34
|
+
- pip install pytest pytest-cov
|
|
35
|
+
- pytest -v test/
|
|
36
|
+
rules:
|
|
37
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
38
|
+
when: manual
|
|
39
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
40
|
+
|
|
41
|
+
publish:
|
|
42
|
+
stage: deploy
|
|
43
|
+
script:
|
|
44
|
+
- TWINE_PASSWORD=${PYPI_TOKEN} TWINE_USERNAME=__token__ python -m twine upload --verbose --repository pypi dist/*
|
|
45
|
+
rules:
|
|
46
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
47
|
+
|
|
48
|
+
pages:
|
|
49
|
+
stage: deploy
|
|
50
|
+
script:
|
|
51
|
+
- apt-get update && apt-get install libxcursor-dev libgl1 libglx-mesa0 xvfb -y
|
|
52
|
+
- conda env create -f environment.yml
|
|
53
|
+
- source activate ply3d
|
|
54
|
+
- which pip
|
|
55
|
+
- export DISPLAY=:99.0
|
|
56
|
+
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
|
|
57
|
+
- sleep 3
|
|
58
|
+
- pip install "mkdocs<=1.6.1" pymdown-extensions mkdocs-material "markdown-exec[ansi]"
|
|
59
|
+
- pip install "mkdocstrings[python]>=0.18" mkdocs-gen-files
|
|
60
|
+
- pip install mkdocs-literate-nav mkdocs-section-index
|
|
61
|
+
- pip install --no-deps .
|
|
62
|
+
- mkdocs build
|
|
63
|
+
- mv ./site public
|
|
64
|
+
artifacts:
|
|
65
|
+
paths:
|
|
66
|
+
- public
|
|
67
|
+
rules:
|
|
68
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
69
|
+
when: manual
|
|
70
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
ply3d-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ply3d
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python library for manipulating PLY 3D geometries of multicellular tissues
|
|
5
|
+
Author-email: Ross Carter <ross.carter@slcu.cam.ac.uk>, John Fozard <john.fozard@jic.ac.uk>, Guillaume Cerutti <guillaume.cerutti@ens-lyon.fr>
|
|
6
|
+
Maintainer-email: Guillaume Cerutti <guillaume.cerutti@ens-lyon.fr>
|
|
7
|
+
Project-URL: Homepage, https://gitlab.com/morphodynamics/mesh/ply3d_python
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: matplotlib
|
|
10
|
+
Requires-Dist: meshio
|
|
11
|
+
Requires-Dist: pyvista[colormaps]
|
|
12
|
+
Requires-Dist: tqdm
|
|
13
|
+
|
|
14
|
+
# PLY 3D
|
|
15
|
+
|
|
16
|
+
A Python library for manipulating PLY 3D geometries of multicellular tissues.
|
|
17
|
+
|
|
18
|
+
## Motivation
|
|
19
|
+
|
|
20
|
+
**PLY 3D** is an extension of the [PLY format](https://paulbourke.net/dataformats/ply/) introduced to store 3D meshes composed of interconnected, arbitrary polyhedral cells. While complying with the specifications of the original format, it introduces a number of specific syntaxes for mesh elements and properties.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
In Python, you can use the library to load and manipulate such meshes, and visualize them with all their properties using `pyvista`:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from ply3d.ply_parser import read_ply_mesh
|
|
28
|
+
|
|
29
|
+
mesh = read_ply_mesh("example_data/CylinderCells.ply")
|
|
30
|
+
# mesh.vertices.points, mesh.volumes.face_indices, ...
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from ply3d.vtk_export import unstructured_grid_from_mesh
|
|
35
|
+
|
|
36
|
+
grid = unstructured_grid_from_mesh(mesh, elements_to_export=['volume'])
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import pyvista as pv
|
|
41
|
+
|
|
42
|
+
plotter = pv.Plotter()
|
|
43
|
+
plotter.add_mesh(
|
|
44
|
+
grid.shrink(0.9),
|
|
45
|
+
scalars='label', cmap='glasbey', clim=(0, 255), show_scalar_bar=False
|
|
46
|
+
)
|
|
47
|
+
plotter.show()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
`ply3d` is available on PyPI (the Python Package Index) and can be installed by running:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install ply3d
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Documentation
|
|
59
|
+
|
|
60
|
+
Please take a look at the [documentation](https://morphodynamics.gitlab.io/mesh/ply3d_python/) for more information on installation and usage
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## Authors
|
|
64
|
+
|
|
65
|
+
This project is developed and maintained by the [Computational Morphodynamics Group](https://computationalmorphodynamics.org/); find out more about the [research groups](https://computationalmorphodynamics.org/research-groups/) involved.
|
ply3d-0.1.0/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# PLY 3D
|
|
2
|
+
|
|
3
|
+
A Python library for manipulating PLY 3D geometries of multicellular tissues.
|
|
4
|
+
|
|
5
|
+
## Motivation
|
|
6
|
+
|
|
7
|
+
**PLY 3D** is an extension of the [PLY format](https://paulbourke.net/dataformats/ply/) introduced to store 3D meshes composed of interconnected, arbitrary polyhedral cells. While complying with the specifications of the original format, it introduces a number of specific syntaxes for mesh elements and properties.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
In Python, you can use the library to load and manipulate such meshes, and visualize them with all their properties using `pyvista`:
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from ply3d.ply_parser import read_ply_mesh
|
|
15
|
+
|
|
16
|
+
mesh = read_ply_mesh("example_data/CylinderCells.ply")
|
|
17
|
+
# mesh.vertices.points, mesh.volumes.face_indices, ...
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from ply3d.vtk_export import unstructured_grid_from_mesh
|
|
22
|
+
|
|
23
|
+
grid = unstructured_grid_from_mesh(mesh, elements_to_export=['volume'])
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import pyvista as pv
|
|
28
|
+
|
|
29
|
+
plotter = pv.Plotter()
|
|
30
|
+
plotter.add_mesh(
|
|
31
|
+
grid.shrink(0.9),
|
|
32
|
+
scalars='label', cmap='glasbey', clim=(0, 255), show_scalar_bar=False
|
|
33
|
+
)
|
|
34
|
+
plotter.show()
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
`ply3d` is available on PyPI (the Python Package Index) and can be installed by running:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install ply3d
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Documentation
|
|
46
|
+
|
|
47
|
+
Please take a look at the [documentation](https://morphodynamics.gitlab.io/mesh/ply3d_python/) for more information on installation and usage
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
## Authors
|
|
51
|
+
|
|
52
|
+
This project is developed and maintained by the [Computational Morphodynamics Group](https://computationalmorphodynamics.org/); find out more about the [research groups](https://computationalmorphodynamics.org/research-groups/) involved.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Generate the code reference pages."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import mkdocs_gen_files
|
|
6
|
+
|
|
7
|
+
nav = mkdocs_gen_files.Nav()
|
|
8
|
+
|
|
9
|
+
root = Path(__file__).parent.parent
|
|
10
|
+
print(root)
|
|
11
|
+
src = root / "src"
|
|
12
|
+
|
|
13
|
+
for path in sorted(src.rglob("*.py")):
|
|
14
|
+
module_path = path.relative_to(src).with_suffix("")
|
|
15
|
+
doc_path = path.relative_to(src).with_suffix(".md")
|
|
16
|
+
full_doc_path = Path("reference", doc_path)
|
|
17
|
+
|
|
18
|
+
parts = tuple(module_path.parts)
|
|
19
|
+
|
|
20
|
+
if parts[-1] == "__init__":
|
|
21
|
+
parts = parts[:-1]
|
|
22
|
+
doc_path = doc_path.with_name("index.md")
|
|
23
|
+
full_doc_path = full_doc_path.with_name("index.md")
|
|
24
|
+
elif parts[-1] == "__main__":
|
|
25
|
+
continue
|
|
26
|
+
|
|
27
|
+
with mkdocs_gen_files.open(full_doc_path, "w") as fd:
|
|
28
|
+
identifier = ".".join(parts)
|
|
29
|
+
print("::: " + identifier, file=fd)
|
|
30
|
+
|
|
31
|
+
mkdocs_gen_files.set_edit_path(full_doc_path, path.relative_to(root))
|
|
32
|
+
|
|
33
|
+
with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file:
|
|
34
|
+
nav_file.writelines(nav.build_literate_nav())
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Importing Meshes
|
|
2
|
+
|
|
3
|
+
There are dozens of existing mesh file formats that are used to store surfacic meshes or volumetric meshes, usually composed of standard volume elements (tetrahedra or hexahedra). Fortunately, the [`meshio`](https://pypi.org/project/meshio/) Python library almost supports them all!
|
|
4
|
+
|
|
5
|
+
## Importing meshes using `meshio`
|
|
6
|
+
|
|
7
|
+
The `ply3d` library relies on `meshio` to provide a generic reader function ([`read_meshio_mesh`](reference/ply3d/meshio_import.md)) that reads virtually any commonly used mesh format and loads it as a [`Mesh`](reference/ply3d/mesh.md) object.
|
|
8
|
+
|
|
9
|
+
```python exec="true" source="console" session="import"
|
|
10
|
+
from ply3d.meshio_import import read_meshio_mesh
|
|
11
|
+
|
|
12
|
+
mesh_filename = "example_data/teapot.obj"
|
|
13
|
+
mesh = read_meshio_mesh(mesh_filename)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```python exec="true" result='python' session="import"
|
|
17
|
+
print(mesh)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```python exec="true" session="import" html="true"
|
|
21
|
+
from io import StringIO
|
|
22
|
+
|
|
23
|
+
import pyvista as pv
|
|
24
|
+
import matplotlib.pyplot as plt
|
|
25
|
+
|
|
26
|
+
from ply3d.vtk_export import unstructured_grid_from_mesh
|
|
27
|
+
|
|
28
|
+
grid = unstructured_grid_from_mesh(mesh)
|
|
29
|
+
|
|
30
|
+
plotter = pv.Plotter(off_screen=True, window_size=(1500, 750))
|
|
31
|
+
|
|
32
|
+
figure = plt.figure(figsize=(9, 4.5))
|
|
33
|
+
ax = figure.gca()
|
|
34
|
+
|
|
35
|
+
plotter.clear_actors()
|
|
36
|
+
plotter.add_mesh(
|
|
37
|
+
grid, color='w'
|
|
38
|
+
)
|
|
39
|
+
plotter.add_mesh(
|
|
40
|
+
grid.extract_all_edges(), color='k',
|
|
41
|
+
render_lines_as_tubes=True, line_width=2
|
|
42
|
+
)
|
|
43
|
+
plotter.view_xy(negative=False)
|
|
44
|
+
plotter.zoom_camera(2.)
|
|
45
|
+
|
|
46
|
+
ax.imshow(plotter.screenshot())
|
|
47
|
+
ax.axis('off')
|
|
48
|
+
|
|
49
|
+
buffer = StringIO()
|
|
50
|
+
plt.savefig(buffer, format="svg")
|
|
51
|
+
print(buffer.getvalue())
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Volumetric meshes
|
|
55
|
+
|
|
56
|
+
In most mesh formats, volume elements (such as tetrahedra) are defined directly as lists of vertices. The PLY 3D format requires however volume elements to be defined by their bounding faces. When the mesh read through `meshio` contains volume elements but not their faces, those are created and appended to the `Mesh` face elements.
|
|
57
|
+
|
|
58
|
+
!!! warning "XDMF time series"
|
|
59
|
+
|
|
60
|
+
The XDMF mesh format supports time-indexed properties on the elements of a shared mesh. While supporting this format, only the data corresponding to the first time point will be stored as properties on the loaded `Mesh` object.
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
The obtained `Mesh` object can then be converted to a PLY 3D file using the [I/O writer functions](io.md).
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# PLY 3D
|
|
2
|
+
|
|
3
|
+
A Python library for manipulating PLY 3D geometries of multicellular tissues.
|
|
4
|
+
|
|
5
|
+
## What is PLY 3D?
|
|
6
|
+
|
|
7
|
+
The **PLY 3D** format is [a proposed extension](https://gitlab.com/morphodynamics/mesh/documentation/-/blob/master/PlyFormat.pdf?ref_type=heads) of the PLY Polygon File Format for meshes that allows to store 3D connected topological structures in a simple and efficient way. As it complies with [the specifications of the PLY format](https://paulbourke.net/dataformats/ply/) it uses the `.ply` extension. However, it introduces a number of specific syntaxes for mesh elements and properties, enabling to conveniently store 3D meshes composed of interconnected, arbitrary polyhedral cells - such as those used to represent multicellular biological tissues.
|
|
8
|
+
|
|
9
|
+
```python exec="true" session="index" html="true"
|
|
10
|
+
from io import StringIO
|
|
11
|
+
|
|
12
|
+
import matplotlib.pyplot as plt
|
|
13
|
+
import pyvista as pv
|
|
14
|
+
|
|
15
|
+
from ply3d.ply_parser import read_ply_mesh
|
|
16
|
+
from ply3d.vtk_export import unstructured_grid_from_mesh
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
ply_filename = "example_data/CylinderCells.ply"
|
|
20
|
+
mesh = read_ply_mesh(ply_filename)
|
|
21
|
+
grid = unstructured_grid_from_mesh(mesh)
|
|
22
|
+
|
|
23
|
+
plotter = pv.Plotter(off_screen=True, window_size=(1500, 1500))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
figure = plt.figure(figsize=(12, 6))
|
|
27
|
+
axes = figure.subplots(1, 2)
|
|
28
|
+
|
|
29
|
+
ax = axes[0]
|
|
30
|
+
plotter.clear_actors()
|
|
31
|
+
plotter.add_mesh(
|
|
32
|
+
grid.extract_cells_by_type(pv.CellType.POLYHEDRON).shrink(0.95),
|
|
33
|
+
scalars='label', cmap='glasbey', clim=(0, 255), show_scalar_bar=False
|
|
34
|
+
)
|
|
35
|
+
plotter.set_focus((0, 0, 0))
|
|
36
|
+
plotter.set_position((0, 1, 1), reset=True)
|
|
37
|
+
plotter.set_viewup((0, -1, 0))
|
|
38
|
+
plotter.zoom_camera(1.2)
|
|
39
|
+
|
|
40
|
+
ax.imshow(plotter.screenshot())
|
|
41
|
+
ax.axis('off')
|
|
42
|
+
|
|
43
|
+
ax = axes[1]
|
|
44
|
+
plotter.clear_actors()
|
|
45
|
+
plotter.add_mesh(grid.glyph(geom=pv.Sphere(radius=0.25), scale=False, orient=False), color='b')
|
|
46
|
+
plotter.add_mesh(grid.extract_cells_by_type(pv.CellType.LINE).extract_geometry().tube(radius=0.1), color='m')
|
|
47
|
+
plotter.add_mesh(
|
|
48
|
+
grid.extract_cells_by_type(pv.CellType.POLYGON).shrink(0.6),
|
|
49
|
+
color='limegreen', opacity=1.,
|
|
50
|
+
style='wireframe', line_width=3
|
|
51
|
+
)
|
|
52
|
+
plotter.add_mesh(
|
|
53
|
+
grid.extract_cells_by_type(pv.CellType.POLYHEDRON).shrink(0.95),
|
|
54
|
+
# scalars='display_label', cmap='glasbey', clim=(0, 255)
|
|
55
|
+
color='w', opacity=0.25
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
plotter.set_focus((0, 0, 0))
|
|
59
|
+
plotter.set_position((0, 1, 1), reset=True)
|
|
60
|
+
plotter.set_viewup((0, -1, 0))
|
|
61
|
+
plotter.zoom_camera(1.2)
|
|
62
|
+
|
|
63
|
+
ax.imshow(plotter.screenshot())
|
|
64
|
+
ax.axis('off')
|
|
65
|
+
|
|
66
|
+
figure.tight_layout()
|
|
67
|
+
|
|
68
|
+
buffer = StringIO()
|
|
69
|
+
plt.savefig(buffer, format="svg")
|
|
70
|
+
print(buffer.getvalue())
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The `ply3d` Python library is a tool for manipulating and interacting with such structures stored as **PLY 3D** files. It provides high-level functions to perform the following tasks:
|
|
74
|
+
<div class="grid cards" markdown>
|
|
75
|
+
|
|
76
|
+
- [:fontawesome-solid-folder-open: **I/O**](io.md)
|
|
77
|
+
|
|
78
|
+
Read and write meshes from / to files in the PLY 3D format
|
|
79
|
+
|
|
80
|
+
- [:fontawesome-solid-circle-nodes: **Data Structure**](mesh.md)
|
|
81
|
+
|
|
82
|
+
Inspect meshes and edit their elements and properties
|
|
83
|
+
|
|
84
|
+
- [:fontawesome-solid-eye: **Visualization**](visualization.md)
|
|
85
|
+
|
|
86
|
+
View meshes with their properties as [`pyvista`](https://pyvista.org/) (VTK) objects
|
|
87
|
+
|
|
88
|
+
- [:fontawesome-solid-file-import: **Conversion**](import.md)
|
|
89
|
+
|
|
90
|
+
Import meshes from other file formats through [`meshio`](https://pypi.org/project/meshio/)
|
|
91
|
+
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
## Installation
|
|
95
|
+
|
|
96
|
+
`ply3d` is available on PyPI (the Python Package Index) and can be installed by running:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pip install ply3d
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The library can also be installed [from sources](https://gitlab.com/morphodynamics/mesh/ply3d_python) after downloading it, by navigating to the project directory (potentially activating a virtual environment) and running:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pip install -e .
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
!!! info "See also"
|
|
109
|
+
|
|
110
|
+
A [C++ header-only parser for the PLY 3D format](https://gitlab.com/morphodynamics/mesh/ply3d) is also available; if this better suits your project's needs, please consider using it!
|
ply3d-0.1.0/docs/io.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# PLY 3D I/O
|
|
2
|
+
|
|
3
|
+
The primary goal of the `ply3d` library is to provide a convenient way of reading PLY 3D mesh files, as well as to export geometries to that format.
|
|
4
|
+
|
|
5
|
+
## Reading a PLY 3D mesh
|
|
6
|
+
|
|
7
|
+
The module [`ply3d.ply_parser`](reference/ply3d/ply_parser.md) provides a parser for both ASCII and binary formatted PLY 3D files, and allows to load them as [`Mesh`](reference/ply3d/mesh.md) objects:
|
|
8
|
+
|
|
9
|
+
```python exec="true" source="console" session="io"
|
|
10
|
+
from ply3d.ply_parser import read_ply_mesh
|
|
11
|
+
|
|
12
|
+
ply_filename = "example_data/hexagonal_prisms.ply"
|
|
13
|
+
mesh = read_ply_mesh(ply_filename)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```python exec="true" result='python' session="io"
|
|
17
|
+
print(mesh)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Python offers the possibility to dynamically inspect the data represented in the `Mesh` data structure. For instance, it is straightforward to access the mesh vertex positions as a Python dictionary (where the keys correspond to the vertex indices):
|
|
21
|
+
|
|
22
|
+
```python exec="true" source="console" session="io"
|
|
23
|
+
mesh.vertices.points
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```python exec="true" result='python' session="io"
|
|
27
|
+
print("{")
|
|
28
|
+
print(f" 0: {mesh.vertices.points[0]},")
|
|
29
|
+
print(f" 1: {mesh.vertices.points[1]},")
|
|
30
|
+
print(" ...")
|
|
31
|
+
print(f" {len(mesh.vertices)-2}: {mesh.vertices.points[len(mesh.vertices)-2]}")
|
|
32
|
+
print(f" {len(mesh.vertices)-1}: {mesh.vertices.points[len(mesh.vertices)-1]}")
|
|
33
|
+
print("}")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Similarly, it is easy to access properties defined of the mesh elements as Python dictionaries. Note that these dictionaries are mutable, and that modifying them will change the data of the `Mesh`.
|
|
37
|
+
|
|
38
|
+
```python exec="true" source="console" session="io"
|
|
39
|
+
mesh.faces.properties['area']
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```python exec="true" result='python' session="io"
|
|
43
|
+
print("{")
|
|
44
|
+
print(f" 0: {mesh.faces.properties['area'][0]},")
|
|
45
|
+
print(f" 1: {mesh.faces.properties['area'][1]},")
|
|
46
|
+
print(" ...")
|
|
47
|
+
print(f" {len(mesh.faces)-2}: {mesh.faces.properties['area'][len(mesh.faces)-2]}")
|
|
48
|
+
print(f" {len(mesh.faces)-1}: {mesh.faces.properties['area'][len(mesh.faces)-1]}")
|
|
49
|
+
print("}")
|
|
50
|
+
```
|
ply3d-0.1.0/docs/mesh.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Mesh Data Structure
|
|
2
|
+
|
|
3
|
+
Meshes in the PLY 3D format are defined by a set of **vertices**, each with a 3D position, together with collections of higher-dimension geometric elements built upon these vertices:
|
|
4
|
+
|
|
5
|
+
- **edges** with their extremities defined by exactly 2 vertices
|
|
6
|
+
- **faces** (polygonal) defined as an ordered list of vertices
|
|
7
|
+
- **volumes** (polyhedral) delimited by an oriented set of faces
|
|
8
|
+
|
|
9
|
+
## Mesh elements
|
|
10
|
+
|
|
11
|
+
The structure of the central [`Mesh`](reference/ply3d/mesh.md) object in `ply3d` reflects this definition, and basically corresponds to a dictionary structure with 4 keys corresponding to the 4 mesh elements of increasing dimensions: `'vertex'`, `'edge'`, `'face'`and `'volume'`:
|
|
12
|
+
|
|
13
|
+
```python exec="true" source="console" session="mesh"
|
|
14
|
+
from ply3d.mesh import Mesh
|
|
15
|
+
|
|
16
|
+
mesh = Mesh(
|
|
17
|
+
vertex_points=[[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]],
|
|
18
|
+
face_vertex_indices=[[0, 1, 2], [1, 3, 2]]
|
|
19
|
+
)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```python exec="true" result='python' session="mesh"
|
|
23
|
+
print(mesh)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
These elements are accessible by their key, just as in a standard Python dictionary:
|
|
27
|
+
|
|
28
|
+
```python exec="true" source="console" session="mesh"
|
|
29
|
+
mesh['vertex']
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```python exec="true" result='python' session="mesh"
|
|
33
|
+
print(mesh['vertex'])
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
But convenience accessors are also provided, using the plural of the element name:
|
|
37
|
+
|
|
38
|
+
```python exec="true" source="console" session="mesh"
|
|
39
|
+
mesh.faces
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```python exec="true" result='python' session="mesh"
|
|
43
|
+
print(mesh.faces)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Element properties
|
|
47
|
+
|
|
48
|
+
Properties can be attached to the mesh elements, as dictionaries indexed by the element **indices**, and identified by a **property name**:
|
|
49
|
+
|
|
50
|
+
```python exec="true" source="console" session="mesh"
|
|
51
|
+
from numpy.random import rand, randint
|
|
52
|
+
|
|
53
|
+
mesh.set_property(
|
|
54
|
+
element_name='face', property_name='random',
|
|
55
|
+
property_dict={f: rand() for f in mesh['face'].indices}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
mesh.set_property(
|
|
59
|
+
element_name='face', property_name='random_list',
|
|
60
|
+
property_dict={f: [rand() for _ in range(randint(2, 5))] for f in mesh['face'].indices}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
mesh.faces
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```python exec="true" result='python' session="mesh"
|
|
67
|
+
print(mesh.faces)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Properties attached to the mesh elements can be accessed just as a regular dictionary:
|
|
71
|
+
|
|
72
|
+
```python exec="true" source="console" session="mesh"
|
|
73
|
+
mesh.faces.properties
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```python exec="true" result='python' session="mesh"
|
|
77
|
+
print("{")
|
|
78
|
+
for i_p, (property_name, property_dict) in enumerate(mesh.faces.properties.items()):
|
|
79
|
+
print(f" '{property_name}': {{")
|
|
80
|
+
for i_v, (key, value) in enumerate(property_dict.items()):
|
|
81
|
+
print(f" {key}: {value}{',' if i_v < len(property_dict)-1 else ''}")
|
|
82
|
+
print(f" }}{',' if i_p < len(mesh.faces.properties)-1 else ''}")
|
|
83
|
+
print("}")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
But in addition, each property has a **type** (more precisely a list of type names) that corresponds to the way it would be typed in a PLY file header:
|
|
87
|
+
|
|
88
|
+
```python exec="true" source="console" session="mesh"
|
|
89
|
+
mesh.faces.properties['random'].property_type
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```python exec="true" result='python' session="mesh"
|
|
93
|
+
print(mesh.faces.properties['random'].property_type)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The type can be passed when setting the property on the mesh, but if none is given, it is guessed from the values of the property dictionary:
|
|
97
|
+
|
|
98
|
+
```python exec="true" source="console" session="mesh"
|
|
99
|
+
mesh.faces.properties['random_list'].property_type
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```python exec="true" result='python' session="mesh"
|
|
103
|
+
print(mesh.faces.properties['random_list'].property_type)
|
|
104
|
+
```
|