pyfmaps 1.2.0__tar.gz → 1.3.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.
- pyfmaps-1.3.0/.github/workflows/documentation.yml +63 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/.github/workflows/test_and_deploy.yml +1 -1
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/.gitignore +9 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/PKG-INFO +49 -27
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/README.md +43 -8
- pyfmaps-1.3.0/docs/source/_static/pyfm.css +24 -0
- pyfmaps-1.3.0/docs/source/_templates/autosummary/base.rst +8 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/docs/source/_templates/autosummary/class.rst +3 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/docs/source/api.rst +12 -0
- pyfmaps-1.3.0/docs/source/conf.py +137 -0
- pyfmaps-1.3.0/docs/source/getting_started.md +105 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/docs/source/index.rst +3 -2
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/docs/source/install.md +11 -1
- pyfmaps-1.3.0/examples/data/cat.obj +21618 -0
- pyfmaps-1.3.0/examples/data/tr_reg_000.off +20668 -0
- pyfmaps-1.3.0/examples/data/tr_reg_040.off +20668 -0
- pyfmaps-1.3.0/examples/data/tr_reg_047.off +20668 -0
- pyfmaps-1.3.0/examples/gallery/GALLERY_HEADER.rst +11 -0
- pyfmaps-1.3.0/examples/gallery/plot_01_load_mesh.py +103 -0
- pyfmaps-1.3.0/examples/gallery/plot_02_mesh_geometry.py +269 -0
- pyfmaps-1.3.0/examples/gallery/plot_03_spectrum.py +351 -0
- pyfmaps-1.3.0/examples/gallery/plot_04_maps_from_correspondence.py +358 -0
- pyfmaps-1.3.0/examples/gallery/plot_05_maps_without_correspondence.py +290 -0
- pyfmaps-1.3.0/examples/gallery/plot_06_functional_map_network.py +340 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/FMN/FMN.py +96 -49
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/_version.py +3 -3
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/functional.py +299 -194
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/mesh/file_utils.py +8 -5
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/mesh/geometry.py +7 -16
- pyfmaps-1.3.0/pyFM/mesh/trimesh.py +1741 -0
- pyfmaps-1.3.0/pyFM/optimize/__init__.py +2 -0
- pyfmaps-1.3.0/pyFM/optimize/weights.py +203 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/refine/icp.py +19 -31
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/refine/zoomout.py +22 -16
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/signatures/HKS_functions.py +6 -4
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/signatures/WKS_functions.py +6 -4
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/spectral/__init__.py +1 -1
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/spectral/convert.py +18 -14
- pyfmaps-1.3.0/pyFM/spectral/nearest_neighbor/__init__.py +26 -0
- pyfmaps-1.3.0/pyFM/spectral/nearest_neighbor/backends.py +152 -0
- pyfmaps-1.3.0/pyFM/spectral/nearest_neighbor/config.py +69 -0
- pyfmaps-1.3.0/pyFM/spectral/nearest_neighbor/dispatch.py +99 -0
- pyfmaps-1.3.0/pyFM/spectral/nn_utils.py +13 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/spectral/projection_utils.py +9 -7
- pyfmaps-1.3.0/pyFM/viz/__init__.py +20 -0
- pyfmaps-1.3.0/pyFM/viz/_convert.py +73 -0
- pyfmaps-1.3.0/pyFM/viz/plot.py +351 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyfmaps.egg-info/PKG-INFO +49 -27
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyfmaps.egg-info/SOURCES.txt +26 -5
- pyfmaps-1.3.0/pyfmaps.egg-info/requires.txt +10 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyfmaps.egg-info/scm_file_list.json +95 -74
- pyfmaps-1.3.0/pyfmaps.egg-info/scm_version.json +8 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyproject.toml +34 -10
- pyfmaps-1.3.0/requirements_docs.txt +12 -0
- pyfmaps-1.3.0/tests/conftest.py +40 -0
- pyfmaps-1.3.0/tests/test_data.py +8 -0
- pyfmaps-1.3.0/tests/test_functional.py +100 -0
- pyfmaps-1.3.0/tests/test_trimesh.py +817 -0
- pyfmaps-1.3.0/uv.lock +5104 -0
- pyfmaps-1.2.0/.github/workflows/documentation.yml +0 -51
- pyfmaps-1.2.0/docs/source/conf.py +0 -76
- pyfmaps-1.2.0/docs/source/examples.md +0 -15
- pyfmaps-1.2.0/docs/source/getting_started.md +0 -89
- pyfmaps-1.2.0/examples/functional_map_network/FMN_base.ipynb +0 -332
- pyfmaps-1.2.0/examples/mesh_and_matching/basic_functions.ipynb +0 -447
- pyfmaps-1.2.0/examples/mesh_and_matching/matching_with_zoomout.ipynb +0 -187
- pyfmaps-1.2.0/pyFM/mesh/trimesh.py +0 -1420
- pyfmaps-1.2.0/pyFM/optimize/__init__.py +0 -1
- pyfmaps-1.2.0/pyFM/spectral/nn_utils.py +0 -40
- pyfmaps-1.2.0/pyfmaps.egg-info/requires.txt +0 -23
- pyfmaps-1.2.0/pyfmaps.egg-info/scm_version.json +0 -8
- pyfmaps-1.2.0/requirements_docs.txt +0 -13
- pyfmaps-1.2.0/tests/test_data.py +0 -8
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/.github/workflows/lint.yml +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/.pre-commit-config.yaml +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/LICENSE +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/MANIFEST.in +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/doc/zoomout.gif +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/docs/source/_static/.gitkeep +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/docs/source/_templates/autosummary/module.rst +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/camel-gallop-01.off +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/camel-gallop-02.off +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/camel-gallop-03.off +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/camel-gallop-04.off +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/camel-gallop-05.off +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/camel-gallop-06.off +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/camel-gallop-07.off +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/camel-gallop-08.off +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/camel-gallop-09.off +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/camel-gallop-10.off +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/10_to_2 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/10_to_8 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/1_to_3 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/1_to_5 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/1_to_7 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/2_to_10 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/2_to_3 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/2_to_4 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/2_to_5 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/3_to_1 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/3_to_2 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/3_to_5 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/3_to_6 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/3_to_9 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/4_to_2 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/4_to_8 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/5_to_1 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/5_to_2 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/5_to_3 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/5_to_7 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/6_to_3 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/6_to_7 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/7_to_1 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/7_to_5 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/7_to_6 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/8_to_10 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/8_to_4 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/8_to_9 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/9_to_3 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/camel_gallop/maps/9_to_8 +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/cat-00.off +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/landmarks.txt +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/lion-00.off +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/examples/data/lion2cat +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/FMN/__init__.py +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/__init__.py +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/eval/__init__.py +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/eval/evaluate.py +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/mesh/__init__.py +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/mesh/data/texture_1.jpg +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/mesh/data/texture_2.jpg +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/mesh/laplacian.py +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/optimize/base_functions.py +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/refine/__init__.py +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/signatures/__init__.py +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyFM/spectral/shape_difference.py +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyfmaps.egg-info/dependency_links.txt +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/pyfmaps.egg-info/top_level.txt +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/requirements.txt +0 -0
- {pyfmaps-1.2.0 → pyfmaps-1.3.0}/setup.cfg +0 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
name: Documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
pull_request:
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
release:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build_documentation:
|
|
13
|
+
name: Build Documentation
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
timeout-minutes: 30
|
|
16
|
+
|
|
17
|
+
permissions:
|
|
18
|
+
contents: write
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0 # setuptools_scm needs tags to compute the version
|
|
24
|
+
|
|
25
|
+
# VTK's rendering libraries link against libGL at import time, and the
|
|
26
|
+
# gallery renders PyVista scenes off-screen. Since VTK 9.5 the stock wheel
|
|
27
|
+
# does headless rendering through EGL, so libegl1 is enough and no Xvfb is
|
|
28
|
+
# needed. NB: libgl1-mesa-glx does not exist on ubuntu-24.04 (what
|
|
29
|
+
# ubuntu-latest now resolves to) -- it is libgl1 + libglx-mesa0.
|
|
30
|
+
- name: Install VTK runtime libraries
|
|
31
|
+
run: |
|
|
32
|
+
sudo apt-get update
|
|
33
|
+
sudo apt-get install -y --no-install-recommends \
|
|
34
|
+
libgl1 libegl1 libglx-mesa0 libxrender1
|
|
35
|
+
|
|
36
|
+
- uses: astral-sh/setup-uv@v5
|
|
37
|
+
with:
|
|
38
|
+
enable-cache: true
|
|
39
|
+
|
|
40
|
+
# dev + docs come from [tool.uv] default-groups; viz via pyfmaps[viz].
|
|
41
|
+
- name: Install dependencies from the lockfile
|
|
42
|
+
run: uv sync --locked
|
|
43
|
+
|
|
44
|
+
# -d keeps Sphinx's doctree cache out of the published directory. It defaults
|
|
45
|
+
# to <outdir>/.doctrees, which means it gets deployed to the website: 12 MB of
|
|
46
|
+
# build artifacts (61% of the push) plus an environment.pickle containing
|
|
47
|
+
# absolute build paths.
|
|
48
|
+
- name: Build HTML
|
|
49
|
+
run: uv run sphinx-build -d docs/_build/doctrees -b html docs/source/ docs/_build/html
|
|
50
|
+
|
|
51
|
+
- name: upload artifact
|
|
52
|
+
uses: actions/upload-artifact@v4
|
|
53
|
+
with:
|
|
54
|
+
path:
|
|
55
|
+
docs/_build/html/
|
|
56
|
+
- name: Deploy to GitHub Pages
|
|
57
|
+
uses: peaceiris/actions-gh-pages@v4
|
|
58
|
+
if: github.ref == 'refs/heads/master'
|
|
59
|
+
with:
|
|
60
|
+
publish_branch: gh-pages
|
|
61
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
62
|
+
publish_dir: docs/_build/html
|
|
63
|
+
force_orphan: true
|
|
@@ -3,6 +3,11 @@ docs/source/generated/*
|
|
|
3
3
|
docs/make.bat
|
|
4
4
|
docs/Makefile
|
|
5
5
|
|
|
6
|
+
# sphinx-gallery output (regenerated on every build)
|
|
7
|
+
docs/source/auto_examples/
|
|
8
|
+
docs/source/gen_modules/
|
|
9
|
+
docs/source/sg_execution_times.rst
|
|
10
|
+
|
|
6
11
|
# Byte-compiled / optimized / DLL files
|
|
7
12
|
__pycache__/
|
|
8
13
|
*.py[cod]
|
|
@@ -82,6 +87,7 @@ target/
|
|
|
82
87
|
# Pycharm and VSCode
|
|
83
88
|
.idea/
|
|
84
89
|
venv/
|
|
90
|
+
.venv/
|
|
85
91
|
.vscode/
|
|
86
92
|
|
|
87
93
|
# IPython Notebook
|
|
@@ -102,3 +108,6 @@ small_test.ipynb
|
|
|
102
108
|
# Dask
|
|
103
109
|
dask-worker-space/
|
|
104
110
|
|
|
111
|
+
|
|
112
|
+
# research scratch, not part of the package
|
|
113
|
+
studies/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyfmaps
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Python bindings for functional maps
|
|
5
5
|
Author: Robin Magnet
|
|
6
6
|
License: MIT License
|
|
@@ -35,13 +35,12 @@ Classifier: Operating System :: OS Independent
|
|
|
35
35
|
Classifier: Programming Language :: Python
|
|
36
36
|
Classifier: Programming Language :: Python :: 3
|
|
37
37
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
38
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
39
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
40
38
|
Classifier: Programming Language :: Python :: 3.10
|
|
41
39
|
Classifier: Programming Language :: Python :: 3.11
|
|
42
40
|
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
43
42
|
Classifier: Topic :: Software Development :: Testing
|
|
44
|
-
Requires-Python: >=3.
|
|
43
|
+
Requires-Python: >=3.10
|
|
45
44
|
Description-Content-Type: text/markdown
|
|
46
45
|
License-File: LICENSE
|
|
47
46
|
Requires-Dist: numpy
|
|
@@ -50,21 +49,9 @@ Requires-Dist: potpourri3d>=1.3
|
|
|
50
49
|
Requires-Dist: robust_laplacian
|
|
51
50
|
Requires-Dist: tqdm
|
|
52
51
|
Requires-Dist: scikit-learn
|
|
53
|
-
Provides-Extra:
|
|
54
|
-
Requires-Dist:
|
|
55
|
-
Requires-Dist:
|
|
56
|
-
Requires-Dist: pytest; extra == "dev"
|
|
57
|
-
Requires-Dist: pytest-cov; extra == "dev"
|
|
58
|
-
Requires-Dist: build; extra == "dev"
|
|
59
|
-
Provides-Extra: docs
|
|
60
|
-
Requires-Dist: furo; extra == "docs"
|
|
61
|
-
Requires-Dist: myst_parser>=0.13; extra == "docs"
|
|
62
|
-
Requires-Dist: sphinx>=4.0; extra == "docs"
|
|
63
|
-
Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
|
|
64
|
-
Requires-Dist: sphinx-copybutton; extra == "docs"
|
|
65
|
-
Requires-Dist: sphinx-design; extra == "docs"
|
|
66
|
-
Requires-Dist: sphinx-gallery; extra == "docs"
|
|
67
|
-
Requires-Dist: sphinx-math-dollar; extra == "docs"
|
|
52
|
+
Provides-Extra: viz
|
|
53
|
+
Requires-Dist: pyvista[jupyter]>=0.44; extra == "viz"
|
|
54
|
+
Requires-Dist: matplotlib; extra == "viz"
|
|
68
55
|
Dynamic: license-file
|
|
69
56
|
|
|
70
57
|
# pyFM - Python Functional Maps Library
|
|
@@ -73,11 +60,15 @@ Dynamic: license-file
|
|
|
73
60
|
<img src="doc/zoomout.gif" alt="animated" />
|
|
74
61
|
</p>
|
|
75
62
|
|
|
76
|
-
[](https://pypi.org/project/pyfmaps/)
|
|
64
|
+
[](https://pypi.org/project/pyfmaps/)
|
|
65
|
+
[](https://github.com/RobinMagnet/pyFM/blob/master/LICENSE)
|
|
66
|
+
[](https://github.com/RobinMagnet/pyFM/actions/workflows/test_and_deploy.yml)
|
|
67
|
+
[](https://robinmagnet.github.io/pyFM/)
|
|
77
68
|
|
|
78
|
-
|
|
69
|
+
This package contains a comprehensive Python implementation for shape correspondence using functional maps, featuring code from [multiple papers](#implemented-papers).
|
|
79
70
|
|
|
80
|
-
|
|
71
|
+
Full [API documentation](https://robinmagnet.github.io/pyFM/) is available online.
|
|
81
72
|
|
|
82
73
|
## Core Features
|
|
83
74
|
- Complete TriMesh class with geometric measures (geodesics, normals, LBO projections, differential operators)
|
|
@@ -90,13 +81,44 @@ This package contains a comprehensive Python implementation for shape correspond
|
|
|
90
81
|
## Installation
|
|
91
82
|
```bash
|
|
92
83
|
pip install pyfmaps
|
|
93
|
-
```
|
|
94
84
|
|
|
95
|
-
|
|
85
|
+
# with the optional PyVista-based plotting helpers used in the gallery
|
|
86
|
+
pip install 'pyfmaps[viz]'
|
|
87
|
+
```
|
|
96
88
|
|
|
97
89
|
### Key Dependencies
|
|
98
|
-
|
|
99
|
-
|
|
90
|
+
Requires Python 3.10+ and: numpy, scipy, tqdm, scikit-learn,
|
|
91
|
+
[`potpourri3d`](https://github.com/nmwsharp/potpourri3d) (geodesic distances via the heat
|
|
92
|
+
and fast-marching methods), and
|
|
93
|
+
[`robust_laplacian`](https://github.com/nmwsharp/robust-laplacians-py) (Delaunay/tufted
|
|
94
|
+
Laplacian). All are installed automatically.
|
|
95
|
+
|
|
96
|
+
## Quickstart
|
|
97
|
+
```python
|
|
98
|
+
from pyFM.mesh import TriMesh
|
|
99
|
+
from pyFM.functional import FunctionalMapping
|
|
100
|
+
|
|
101
|
+
# Load a source and a target mesh
|
|
102
|
+
mesh1 = TriMesh.load("cat.off", area_normalize=True, center=True)
|
|
103
|
+
mesh2 = TriMesh.load("lion.off", area_normalize=True, center=True)
|
|
104
|
+
|
|
105
|
+
# Compute the LBO spectrum + descriptors, then fit a functional map.
|
|
106
|
+
# Keep w_orient above 0: descriptors alone cannot tell left from right,
|
|
107
|
+
# and w_orient=0 (the default) usually returns a symmetry-flipped map.
|
|
108
|
+
model = FunctionalMapping(mesh1, mesh2)
|
|
109
|
+
model.preprocess(descr_type="WKS", subsample_step=2, verbose=True)
|
|
110
|
+
model.fit(K=30, w_descr=1e0, w_lap=1e-1, w_dcomm=1e0, w_orient=1e0, verbose=True)
|
|
111
|
+
|
|
112
|
+
# Convert the functional map to a point-to-point map (mesh2 -> mesh1)
|
|
113
|
+
p2p_21 = model.get_p2p()
|
|
114
|
+
|
|
115
|
+
# Refine the map -- this is where most of the accuracy comes from
|
|
116
|
+
FM_zo = model.zoomout_refine(nit=16, step=5)
|
|
117
|
+
p2p_21_zo = model.get_p2p(FM_zo)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
See the [example gallery](https://robinmagnet.github.io/pyFM/auto_examples/index.html) for
|
|
121
|
+
complete, runnable workflows with interactive 3D figures.
|
|
100
122
|
|
|
101
123
|
## Design Philosophy
|
|
102
124
|
This codebase prioritizes readability and adaptability over rigid modularity.
|
|
@@ -125,7 +147,7 @@ I selected this approach from my personal experience with research codebases and
|
|
|
125
147
|
|
|
126
148
|
## Documentation & Examples
|
|
127
149
|
- [API Documentation](https://robinmagnet.github.io/pyFM/)
|
|
128
|
-
- [Example
|
|
150
|
+
- [Example Gallery](https://robinmagnet.github.io/pyFM/auto_examples/index.html)
|
|
129
151
|
|
|
130
152
|
## Implemented Papers
|
|
131
153
|
This library implements methods from several key papers in shape correspondence, including:
|
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
<img src="doc/zoomout.gif" alt="animated" />
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
[](https://pypi.org/project/pyfmaps/)
|
|
8
|
+
[](https://pypi.org/project/pyfmaps/)
|
|
9
|
+
[](https://github.com/RobinMagnet/pyFM/blob/master/LICENSE)
|
|
10
|
+
[](https://github.com/RobinMagnet/pyFM/actions/workflows/test_and_deploy.yml)
|
|
11
|
+
[](https://robinmagnet.github.io/pyFM/)
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
This package contains a comprehensive Python implementation for shape correspondence using functional maps, featuring code from [multiple papers](#implemented-papers).
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
Full [API documentation](https://robinmagnet.github.io/pyFM/) is available online.
|
|
12
16
|
|
|
13
17
|
## Core Features
|
|
14
18
|
- Complete TriMesh class with geometric measures (geodesics, normals, LBO projections, differential operators)
|
|
@@ -21,13 +25,44 @@ This package contains a comprehensive Python implementation for shape correspond
|
|
|
21
25
|
## Installation
|
|
22
26
|
```bash
|
|
23
27
|
pip install pyfmaps
|
|
24
|
-
```
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
# with the optional PyVista-based plotting helpers used in the gallery
|
|
30
|
+
pip install 'pyfmaps[viz]'
|
|
31
|
+
```
|
|
27
32
|
|
|
28
33
|
### Key Dependencies
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
Requires Python 3.10+ and: numpy, scipy, tqdm, scikit-learn,
|
|
35
|
+
[`potpourri3d`](https://github.com/nmwsharp/potpourri3d) (geodesic distances via the heat
|
|
36
|
+
and fast-marching methods), and
|
|
37
|
+
[`robust_laplacian`](https://github.com/nmwsharp/robust-laplacians-py) (Delaunay/tufted
|
|
38
|
+
Laplacian). All are installed automatically.
|
|
39
|
+
|
|
40
|
+
## Quickstart
|
|
41
|
+
```python
|
|
42
|
+
from pyFM.mesh import TriMesh
|
|
43
|
+
from pyFM.functional import FunctionalMapping
|
|
44
|
+
|
|
45
|
+
# Load a source and a target mesh
|
|
46
|
+
mesh1 = TriMesh.load("cat.off", area_normalize=True, center=True)
|
|
47
|
+
mesh2 = TriMesh.load("lion.off", area_normalize=True, center=True)
|
|
48
|
+
|
|
49
|
+
# Compute the LBO spectrum + descriptors, then fit a functional map.
|
|
50
|
+
# Keep w_orient above 0: descriptors alone cannot tell left from right,
|
|
51
|
+
# and w_orient=0 (the default) usually returns a symmetry-flipped map.
|
|
52
|
+
model = FunctionalMapping(mesh1, mesh2)
|
|
53
|
+
model.preprocess(descr_type="WKS", subsample_step=2, verbose=True)
|
|
54
|
+
model.fit(K=30, w_descr=1e0, w_lap=1e-1, w_dcomm=1e0, w_orient=1e0, verbose=True)
|
|
55
|
+
|
|
56
|
+
# Convert the functional map to a point-to-point map (mesh2 -> mesh1)
|
|
57
|
+
p2p_21 = model.get_p2p()
|
|
58
|
+
|
|
59
|
+
# Refine the map -- this is where most of the accuracy comes from
|
|
60
|
+
FM_zo = model.zoomout_refine(nit=16, step=5)
|
|
61
|
+
p2p_21_zo = model.get_p2p(FM_zo)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
See the [example gallery](https://robinmagnet.github.io/pyFM/auto_examples/index.html) for
|
|
65
|
+
complete, runnable workflows with interactive 3D figures.
|
|
31
66
|
|
|
32
67
|
## Design Philosophy
|
|
33
68
|
This codebase prioritizes readability and adaptability over rigid modularity.
|
|
@@ -56,7 +91,7 @@ I selected this approach from my personal experience with research codebases and
|
|
|
56
91
|
|
|
57
92
|
## Documentation & Examples
|
|
58
93
|
- [API Documentation](https://robinmagnet.github.io/pyFM/)
|
|
59
|
-
- [Example
|
|
94
|
+
- [Example Gallery](https://robinmagnet.github.io/pyFM/auto_examples/index.html)
|
|
60
95
|
|
|
61
96
|
## Implemented Papers
|
|
62
97
|
This library implements methods from several key papers in shape correspondence, including:
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/* PyVista interactive scenes (sphinx-gallery + pyvista.ext.viewer_directive).
|
|
2
|
+
*
|
|
3
|
+
* The viewer directive hardcodes width='100%%' (a literal double percent, see
|
|
4
|
+
* pyvista/ext/viewer_directive.py). Browsers reject that as an invalid length
|
|
5
|
+
* and fall back to the default iframe width, so the scene renders in a narrow
|
|
6
|
+
* box. Force a sane size here rather than patching PyVista.
|
|
7
|
+
*/
|
|
8
|
+
iframe[src*="static_viewer.html"] {
|
|
9
|
+
width: 100%;
|
|
10
|
+
min-height: 400px;
|
|
11
|
+
border: 1px solid var(--color-background-border);
|
|
12
|
+
border-radius: 4px;
|
|
13
|
+
background: #ffffff;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* Keep the Static/Interactive tab panes from collapsing around the iframe. */
|
|
17
|
+
.sd-tab-content:has(> iframe[src*="static_viewer.html"]) {
|
|
18
|
+
padding: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Wide outputs (arrays, timing tables) scroll instead of pushing the page. */
|
|
22
|
+
.sphx-glr-script-out pre {
|
|
23
|
+
overflow-x: auto;
|
|
24
|
+
}
|
|
@@ -91,6 +91,18 @@ Laplacians, and mesh file readers/writers.
|
|
|
91
91
|
pyFM.mesh.laplacian
|
|
92
92
|
pyFM.mesh.file_utils
|
|
93
93
|
|
|
94
|
+
Visualization
|
|
95
|
+
-------------
|
|
96
|
+
|
|
97
|
+
PyVista-based plotting helpers for meshes, point clouds and vector fields.
|
|
98
|
+
Requires the ``viz`` extra (``pip install 'pyfmaps[viz]'``).
|
|
99
|
+
|
|
100
|
+
.. autosummary::
|
|
101
|
+
:toctree: generated
|
|
102
|
+
:recursive:
|
|
103
|
+
|
|
104
|
+
pyFM.viz
|
|
105
|
+
|
|
94
106
|
Optimization internals
|
|
95
107
|
----------------------
|
|
96
108
|
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Configuration file for the Sphinx documentation builder.
|
|
2
|
+
#
|
|
3
|
+
# For the full list of built-in configuration values, see the documentation:
|
|
4
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
import importlib.metadata
|
|
8
|
+
import os
|
|
9
|
+
import pathlib
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, pathlib.Path(__file__).parents[2].resolve().as_posix())
|
|
14
|
+
|
|
15
|
+
# -- Project information -----------------------------------------------------
|
|
16
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
|
17
|
+
|
|
18
|
+
project = "pyfmaps"
|
|
19
|
+
copyright = "2024, Robin Magnet"
|
|
20
|
+
author = "Robin Magnet"
|
|
21
|
+
try:
|
|
22
|
+
release = importlib.metadata.version("pyfmaps")
|
|
23
|
+
except importlib.metadata.PackageNotFoundError:
|
|
24
|
+
release = "0.0.0"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# -- General configuration ---------------------------------------------------
|
|
28
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
|
29
|
+
|
|
30
|
+
extensions = [
|
|
31
|
+
"sphinx.ext.duration",
|
|
32
|
+
"sphinx.ext.doctest",
|
|
33
|
+
"sphinx.ext.autodoc",
|
|
34
|
+
"sphinx.ext.autosummary",
|
|
35
|
+
"sphinx.ext.napoleon",
|
|
36
|
+
"sphinx.ext.intersphinx",
|
|
37
|
+
"sphinx_autodoc_typehints", # merge type hints into descriptions (after napoleon)
|
|
38
|
+
"sphinx_math_dollar",
|
|
39
|
+
"sphinx.ext.mathjax",
|
|
40
|
+
"sphinx_copybutton", # copy button on code blocks
|
|
41
|
+
"myst_parser",
|
|
42
|
+
"sphinx_design",
|
|
43
|
+
"sphinx_gallery.gen_gallery",
|
|
44
|
+
"pyvista.ext.viewer_directive", # renders the .vtksz scenes the scraper emits
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# -- Example gallery (sphinx-gallery + PyVista) ------------------------------
|
|
49
|
+
# The gallery executes examples/gallery/plot_*.py at build time and scrapes the
|
|
50
|
+
# scenes their pl.show() calls produce.
|
|
51
|
+
|
|
52
|
+
import pyvista as pv # noqa: E402
|
|
53
|
+
from pyvista.plotting.utilities.sphinx_gallery import DynamicScraper # noqa: E402
|
|
54
|
+
|
|
55
|
+
pv.BUILDING_GALLERY = True
|
|
56
|
+
pv.OFF_SCREEN = True
|
|
57
|
+
pv.set_plot_theme("document")
|
|
58
|
+
pv.global_theme.window_size = [1024, 768]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def reset_pyvista(gallery_conf, fname):
|
|
62
|
+
"""Keep one example's theme/plotter state from leaking into the next."""
|
|
63
|
+
pv.close_all()
|
|
64
|
+
pv.set_plot_theme("document")
|
|
65
|
+
pv.global_theme.window_size = [1024, 768]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
sphinx_gallery_conf = {
|
|
69
|
+
"examples_dirs": "../../examples/gallery",
|
|
70
|
+
"gallery_dirs": "auto_examples",
|
|
71
|
+
# Only plot_*.py are executed. os.sep keeps this working on Windows.
|
|
72
|
+
"filename_pattern": re.escape(os.sep) + r"plot_",
|
|
73
|
+
# The default ("NumberOfCodeLinesSortKey") orders the gallery by script length.
|
|
74
|
+
# Sorting by file name lets the file names encode the reading order instead.
|
|
75
|
+
"within_subsection_order": "FileNameSortKey",
|
|
76
|
+
"image_scrapers": (DynamicScraper(), "matplotlib"),
|
|
77
|
+
"doc_module": ("pyFM",),
|
|
78
|
+
# Rendered by the ``minigallery`` directive in _templates/autosummary/.
|
|
79
|
+
"backreferences_dir": "gen_modules/backreferences",
|
|
80
|
+
# The default is ("matplotlib", "seaborn") -- keep matplotlib's reset, since
|
|
81
|
+
# setting this key replaces the list rather than extending it.
|
|
82
|
+
"reset_modules": ("matplotlib", reset_pyvista),
|
|
83
|
+
"reset_modules_order": "both",
|
|
84
|
+
"remove_config_comments": True,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# sphinx_gallery_conf holds a scraper instance and a reset callback, so Sphinx
|
|
88
|
+
# cannot pickle it into the config cache. Harmless, but noisy on every build.
|
|
89
|
+
suppress_warnings = ["config.cache"]
|
|
90
|
+
|
|
91
|
+
# Resolve :class:/:meth: roles pointing at our dependencies. A short timeout keeps
|
|
92
|
+
# an unreachable inventory to a warning rather than a stalled build.
|
|
93
|
+
intersphinx_mapping = {
|
|
94
|
+
"python": ("https://docs.python.org/3", None),
|
|
95
|
+
"numpy": ("https://numpy.org/doc/stable", None),
|
|
96
|
+
"scipy": ("https://docs.scipy.org/doc/scipy", None),
|
|
97
|
+
"matplotlib": ("https://matplotlib.org/stable", None),
|
|
98
|
+
"pyvista": ("https://docs.pyvista.org", None),
|
|
99
|
+
}
|
|
100
|
+
intersphinx_timeout = 10
|
|
101
|
+
|
|
102
|
+
# Napoleon (numpydoc parsing)
|
|
103
|
+
napoleon_google_docstring = False
|
|
104
|
+
napoleon_numpy_docstring = True
|
|
105
|
+
napoleon_use_param = True
|
|
106
|
+
napoleon_use_rtype = True
|
|
107
|
+
|
|
108
|
+
# Members are documented on their own per-object pages (numpy-style), so module
|
|
109
|
+
# and class pages only render the module/class docstring + summary tables.
|
|
110
|
+
autodoc_default_options = {"member-order": "bysource"}
|
|
111
|
+
autosummary_generate = True
|
|
112
|
+
# Honour each module's __all__ so only intended public names are documented
|
|
113
|
+
# (default True *ignores* __all__, which would leak imported numpy/tqdm/sklearn names).
|
|
114
|
+
autosummary_ignore_module_all = False
|
|
115
|
+
|
|
116
|
+
templates_path = ["_templates"]
|
|
117
|
+
exclude_patterns = []
|
|
118
|
+
|
|
119
|
+
source_suffix = [".rst", ".md"]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# -- Options for HTML output -------------------------------------------------
|
|
123
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
|
124
|
+
|
|
125
|
+
html_theme = "furo"
|
|
126
|
+
html_static_path = ["_static"]
|
|
127
|
+
html_css_files = ["pyfm.css"]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
from sphinx.ext.autodoc import between
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def setup(app):
|
|
134
|
+
# Register a sphinx.ext.autodoc.between listener to ignore everything
|
|
135
|
+
# between lines that contain the word IGNORE
|
|
136
|
+
app.connect("autodoc-process-docstring", between("^.*IGNORE.*$", exclude=True))
|
|
137
|
+
return {"parallel_read_safe": True}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Getting Started
|
|
2
|
+
===============
|
|
3
|
+
|
|
4
|
+
This short tutorial computes a functional map between two meshes and converts it
|
|
5
|
+
to a point-to-point correspondence. It is the condensed version of
|
|
6
|
+
[Computing a map without a correspondence](auto_examples/plot_05_maps_without_correspondence),
|
|
7
|
+
which explains every step and shows what each one buys.
|
|
8
|
+
|
|
9
|
+
Loading meshes
|
|
10
|
+
--------------
|
|
11
|
+
|
|
12
|
+
A `TriMesh` is read from a file with `TriMesh.load`, or built directly from raw vertex/face arrays:
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from pyFM.mesh import TriMesh
|
|
16
|
+
|
|
17
|
+
mesh1 = TriMesh.load("cat-00.off", area_normalize=True, center=True)
|
|
18
|
+
mesh2 = TriMesh.load("lion-00.off", area_normalize=True, center=True)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Computing a functional map
|
|
22
|
+
--------------------------
|
|
23
|
+
|
|
24
|
+
`FunctionalMapping` drives the standard workflow: compute the Laplace-Beltrami
|
|
25
|
+
spectrum and descriptors with `preprocess`, then optimize the map with `fit`.
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from pyFM.functional import FunctionalMapping
|
|
29
|
+
|
|
30
|
+
model = FunctionalMapping(mesh1, mesh2)
|
|
31
|
+
|
|
32
|
+
# Spectrum + descriptors
|
|
33
|
+
model.preprocess(
|
|
34
|
+
descr_type="WKS", # "WKS" or "HKS"
|
|
35
|
+
subsample_step=2, # keep every 2nd descriptor
|
|
36
|
+
verbose=True,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Optimize the functional map
|
|
40
|
+
model.fit(
|
|
41
|
+
K=30, # eigenvectors kept on each shape
|
|
42
|
+
w_descr=1e0, # descriptor preservation
|
|
43
|
+
w_lap=1e-1, # Laplacian commutativity
|
|
44
|
+
w_dcomm=1e0, # descriptor commutativity
|
|
45
|
+
w_orient=1e0, # orientation term
|
|
46
|
+
verbose=True,
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Keep `w_orient` above zero. Descriptors alone cannot tell the left side of a shape
|
|
51
|
+
from its right, so with `w_orient=0` the optimization typically returns a
|
|
52
|
+
symmetry-flipped map that refinement will not fix.
|
|
53
|
+
|
|
54
|
+
Getting a point-to-point map
|
|
55
|
+
----------------------------
|
|
56
|
+
|
|
57
|
+
`get_p2p` converts the functional map `model.FM_12` into a vertex-to-vertex map
|
|
58
|
+
`p2p_21` (for each vertex of mesh2, the index of its match on mesh1):
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
p2p_21 = model.get_p2p()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Refinement
|
|
65
|
+
----------
|
|
66
|
+
|
|
67
|
+
The output of `fit` is a starting point, not an answer — refinement is where most
|
|
68
|
+
of the accuracy comes from. ICP and ZoomOut both return a new functional map
|
|
69
|
+
rather than overwriting `model.FM_12`:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
FM_icp = model.icp_refine(verbose=True)
|
|
73
|
+
p2p_21_icp = model.get_p2p(FM_icp)
|
|
74
|
+
|
|
75
|
+
FM_zo = model.zoomout_refine(nit=16, step=5, verbose=True)
|
|
76
|
+
p2p_21_zo = model.get_p2p(FM_zo)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
ZoomOut grows the map by `step` at each of its `nit` iterations, so `K = 30` ends
|
|
80
|
+
at `30 + 16 * 5 = 110`.
|
|
81
|
+
|
|
82
|
+
Evaluating accuracy
|
|
83
|
+
-------------------
|
|
84
|
+
|
|
85
|
+
Given an (approximate) ground-truth map and a geodesic distance matrix on the
|
|
86
|
+
source mesh, `pyFM.eval.accuracy` reports the mean geodesic error:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
import numpy as np
|
|
90
|
+
import pyFM.eval
|
|
91
|
+
|
|
92
|
+
A_geod = mesh1.geodesic_matrix()
|
|
93
|
+
gt_p2p = np.loadtxt("lion2cat", dtype=int)
|
|
94
|
+
|
|
95
|
+
for name, p2p in [("fit", p2p_21), ("ICP", p2p_21_icp), ("ZoomOut", p2p_21_zo)]:
|
|
96
|
+
acc = pyFM.eval.accuracy(p2p, gt_p2p, A_geod, sqrt_area=mesh1.sqrt_area)
|
|
97
|
+
print(f"{name:8s}: {1e2 * acc:.2f}")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
On this pair that prints roughly `23.85`, `7.39` and `8.53`, in percent of
|
|
101
|
+
$\sqrt{\text{area}}$: the raw fit is coarse, and refinement is what makes it usable.
|
|
102
|
+
|
|
103
|
+
A few matched landmarks, passed to `preprocess` as `landmarks=`, cut the error
|
|
104
|
+
further still — see
|
|
105
|
+
[Computing a map without a correspondence](auto_examples/plot_05_maps_without_correspondence).
|
|
@@ -43,7 +43,8 @@ Whether you are an academic researcher exploring functional map theory or an ind
|
|
|
43
43
|
What’s Next?
|
|
44
44
|
------------
|
|
45
45
|
|
|
46
|
-
To get started with `pyFM`,
|
|
46
|
+
To get started with `pyFM`, work through :doc:`getting_started`, then browse the :ref:`gallery` for runnable
|
|
47
|
+
examples with interactive 3D figures.
|
|
47
48
|
|
|
48
49
|
.. To get started with `pyFM`, check out the [Installation Guide](#installation) and the [Quickstart Tutorial](#getting-started) for a hands-on introduction. For a deeper dive, explore the [API Reference](#detailed-api-reference) and [Tutorials](#tutorials) to learn more about advanced features and workflows.
|
|
49
50
|
|
|
@@ -57,7 +58,7 @@ Table of Contents
|
|
|
57
58
|
|
|
58
59
|
install.md
|
|
59
60
|
getting_started.md
|
|
60
|
-
|
|
61
|
+
auto_examples/index
|
|
61
62
|
|
|
62
63
|
.. toctree::
|
|
63
64
|
:maxdepth: 2
|
|
@@ -2,7 +2,7 @@ Installation
|
|
|
2
2
|
============
|
|
3
3
|
|
|
4
4
|
This package is pretty minimal and should run on all machines. It supports
|
|
5
|
-
Python 3.
|
|
5
|
+
Python 3.10+.
|
|
6
6
|
|
|
7
7
|
With pip
|
|
8
8
|
--------
|
|
@@ -11,6 +11,16 @@ With pip
|
|
|
11
11
|
pip install pyfmaps
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
+
Plotting
|
|
15
|
+
--------
|
|
16
|
+
|
|
17
|
+
The plotting helpers in {mod}`pyFM.viz`, used throughout the
|
|
18
|
+
[example gallery](auto_examples/index), are optional and pull in PyVista:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install 'pyfmaps[viz]'
|
|
22
|
+
```
|
|
23
|
+
|
|
14
24
|
From source
|
|
15
25
|
-----------
|
|
16
26
|
|