├── .gitattributes
├── .gitignore
├── .vscode
├── launch.json
└── settings.json
├── README.md
├── data
├── gifs
│ └── triceratops_gif_small.gif
├── objs
│ ├── bull_initmesh.obj
│ ├── cylinder.obj
│ ├── g_initmesh.obj
│ ├── giraffe_initmesh.obj
│ ├── guitar_initmesh.obj
│ ├── icosahedron.obj
│ ├── remeshed_elephant_hull.obj
│ ├── tetrahedron.obj
│ ├── tiki_initmesh.obj
│ └── triceratops_initmesh.obj
└── point_clouds
│ ├── ankylosaurus.pwn
│ ├── ankylosaurus_settings.json
│ ├── bull.pwn
│ ├── bull_settings.json
│ ├── elephant.pwn
│ ├── elephant_settings.json
│ ├── g.pwn
│ ├── giraffe.pwn
│ ├── giraffe_settings.json
│ ├── guitar.pwn
│ ├── guitar_settings.json
│ ├── guitar_settings_pooling.json
│ ├── hand.pwn
│ ├── hand_settings.json
│ ├── sphere.pwn
│ ├── tiki.pwn
│ ├── tiki_settings.json
│ ├── triceratops.pwn
│ └── triceratops_settings.json
├── requirements.txt
└── source
├── __init__.py
├── layers
├── __init__.py
├── composite
│ ├── ConvolutionSequence.py
│ ├── Decoder.py
│ ├── DownConvolution.py
│ ├── Encoder.py
│ ├── UpConvolution.py
│ ├── __init__.py
│ └── __tests__
│ │ ├── TestConvolutionSequence.py
│ │ ├── TestDecoder.py
│ │ ├── TestDownConvolution.py
│ │ ├── TestEncoder.py
│ │ ├── TestUpConvolution.py
│ │ └── __init__.py
├── convolution
│ ├── MeshConvolution.py
│ ├── __init__.py
│ └── __tests__
│ │ ├── TestMeshConvolution.py
│ │ └── __init__.py
├── feature.py
└── pooling
│ ├── CollapseSnapshot.py
│ ├── MeshPool.py
│ ├── MeshUnpool.py
│ ├── __init__.py
│ ├── __tests__
│ ├── TestCollapseEdge.py
│ ├── TestMeshPool.py
│ ├── TestMeshUnpool.py
│ └── __init__.py
│ └── collapse_edge.py
├── loss
├── ChamferLossLayer.py
├── ConvergenceDetector.py
├── __init__.py
├── __tests__
│ ├── TestChamferLoss.py
│ ├── TestLoss.py
│ └── __init__.py
└── loss.py
├── mesh
├── EdgeConnection.py
├── Mesh.py
├── MeshChecker.py
├── Obj.py
├── __init__.py
├── __tests__
│ ├── TestMesh.py
│ └── __init__.py
└── remesh.py
├── model
├── PointToMeshModel.py
├── __init__.py
├── __tests__
│ ├── TestGetVertexFeatures.py
│ └── __init__.py
└── get_vertex_features.py
├── options
└── options.py
└── script_modules
├── __init__.py
├── create_convex_hull.py
├── make_c4d_point_cloud_ascii.py
├── remesh_obj.py
└── train_model.py
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pwn linguist-detectable=false
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
98 | __pypackages__/
99 |
100 | # Celery stuff
101 | celerybeat-schedule
102 | celerybeat.pid
103 |
104 | # SageMath parsed files
105 | *.sage.py
106 |
107 | # Environments
108 | .env
109 | .venv
110 | env/
111 | venv/
112 | ENV/
113 | env.bak/
114 | venv.bak/
115 |
116 | # Spyder project settings
117 | .spyderproject
118 | .spyproject
119 |
120 | # Rope project settings
121 | .ropeproject
122 |
123 | # mkdocs documentation
124 | /site
125 |
126 | # mypy
127 | .mypy_cache/
128 | .dmypy.json
129 | dmypy.json
130 |
131 | # Pyre type checker
132 | .pyre/
133 |
134 | # pytype static type analyzer
135 | .pytype/
136 |
137 | # Cython debug symbols
138 | cython_debug/
139 |
140 | # MacOS
141 | .DS_Store
142 |
143 | # Temporary script outputs
144 | tmp*
145 |
146 | # Manifold software
147 | Manifold
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Create Convex Hull",
9 | "type": "python",
10 | "request": "launch",
11 | "module": "source.script_modules.create_convex_hull",
12 | "args": [
13 | "--input",
14 | "data/point_clouds/elephant.pwn",
15 | "--output",
16 | "tmp_convex_hull.obj"
17 | ]
18 | },
19 | {
20 | "name": "Remesh Convex Hull",
21 | "type": "python",
22 | "request": "launch",
23 | "module": "source.script_modules.remesh_obj",
24 | "args": [
25 | "--input",
26 | "tmp_convex_hull.obj",
27 | "--output",
28 | "tmp_remeshed_convex_hull.obj"
29 | ]
30 | },
31 | {
32 | "name": "Train Model",
33 | "type": "python",
34 | "request": "launch",
35 | "module": "source.script_modules.train_model",
36 | "args": ["data/point_clouds/elephant_settings.json"]
37 | }
38 | ]
39 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.pythonPath": "venv/bin/python3",
3 | "python.formatting.provider": "black",
4 | "files.exclude": {
5 | "**/.git": true,
6 | "**/.svn": true,
7 | "**/.hg": true,
8 | "**/CVS": true,
9 | "**/.DS_Store": true,
10 | "**/*.pyc": true,
11 | "**/__pycache__": true,
12 | "**/__init__.py": true,
13 | },
14 | "search.exclude": {
15 | "**/node_modules": true,
16 | "**/bower_components": true,
17 | "**/*.code-search": true,
18 | "**/env": true,
19 | "Manifold": true,
20 | },
21 | "python.languageServer": "Pylance",
22 | "python.testing.unittestArgs": [
23 | "-v",
24 | "-s",
25 | ".",
26 | "-p",
27 | "Test*.py"
28 | ],
29 | "python.testing.pytestEnabled": false,
30 | "python.testing.nosetestsEnabled": false,
31 | "python.testing.unittestEnabled": true,
32 | "editor.rulers": [80],
33 | "editor.formatOnSave": true,
34 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Point2Mesh Reimplementation
2 |
3 | 
4 |
5 | This is a reimplementation of [Point2Mesh: A Self-Prior for Deformable Meshes](https://ranahanocka.github.io/point2mesh/) by Rana Hanocka, Gal Metzer, Raja Giryes and Daniel Cohen-Or. It was written by David Charatan, Solon James and Grace Deng as a final project for CSCI 1470: Deep Learning at Brown University. The authors' original implementation (in PyTorch) can be found [here](https://github.com/ranahanocka/Point2Mesh/). Watch a short video we made about the project [here](https://youtu.be/k34zExyJHvo).
6 |
7 | ## Running the Code
8 |
9 | Several example point clouds can be found in the `data` folder. To fit to a point cloud, do the following:
10 |
11 | ```
12 | python3 -m source.script_modules.train_model data/point_clouds/elephant_settings.json
13 | ```
14 |
15 | Replace `elephant_settings.json` with a JSON settings file of your choice. For more information about valid JSON settings files, see `options.py`.
16 |
17 | ## Notes About this Implementation
18 |
19 | This implementation runs slower than the original implementation and doesn't include the PartMesh structure (splitting meshes into parts so that larger meshes can be optimized). However, we tried to add lots of comments to the code to make this implementation easier to follow than the original.
20 |
21 | ## Project Setup
22 |
23 | ### Creating a Virtual Environment
24 |
25 | To create a virtual environment, run `python3 -m venv venv`. Then, do `source venv/bin/activate` (or equivalent) and `pip3 install -r requirements.txt` to install the project's dependencies.
26 |
27 | ### Manifold Software Dependency
28 |
29 | The remeshing and simplification operations Point2Mesh depends on require [this watertight manifold software](https://github.com/hjwdzh/Manifold). To install it, `cd` into the `point2mesh-reimplementation` folder and run the following:
30 |
31 | ```
32 | git clone --recursive -j8 git://github.com/hjwdzh/Manifold
33 | cd Manifold
34 | mkdir build
35 | cd build
36 | cmake .. -DCMAKE_BUILD_TYPE=Release
37 | make
38 | ```
39 |
40 | ### Installing OpenEXR
41 |
42 | OpenEXR, a dependency of TensorFlow Graphics, cannot be installed directly via `pip`. Instead, follow the steps below:
43 |
44 | #### Windows
45 |
46 | Download a precompiled wheel file from `https://www.lfd.uci.edu/~gohlke/pythonlibs/`, move it to the workspace folder, then run `python -m pip install SomePackage-1.0-py2.py3-none-any.whl`. Running `pip3 install tensorflow-graphics` should then work.
47 |
48 | #### MacOS
49 |
50 | ```
51 | brew install openexr
52 | export CFLAGS="-I/Users/USERNAME/homebrew/include/OpenEXR -std=c++11"
53 | export LDFLAGS="-L/Users/USERNAME/homebrew/lib"
54 | pip3 install tensorflow-graphics
55 | ```
56 |
57 | #### Ubuntu
58 |
59 | ```
60 | sudo apt-get install python3-dev
61 | sudo apt-get install libopenexr-dev
62 | sudo apt-get install openexr
63 | pip3 install tensorflow-graphics
64 | ```
65 |
66 | ### Running Scripts
67 |
68 | The scripts in `script_modules` can be run as Python modules. To run `create_convex_hull.py`, run the command `python3 -m source.script_modules.create_convex_hull` from the project's root directory. Several VS Code run configurations are included in `.vscode/launch.json`.
69 |
70 | ### Running Unit Tests
71 |
72 | To run the unit tests in VS Code, open the command menu (⌘ Command ⇧ Shift P on MacOS) and run `Python: Discover Tests` and then `Python: Run All Tests`. I recommend discovering and running tests through Python Test Explorer for Visual Studio Code (`littlefoxteam.vscode-python-test-adapter`) if you add more tests, since it's hard to diagnose broken tests that aren't being discovered with the default test explorer.
73 |
74 | ### GCP VM Creation Steps
75 |
76 | Disclaimer: This is a bit hacky since GCP's setup precludes usage of the normal venv setup.
77 |
78 | First, make a VM:
79 |
80 | - Region: `us-west1 (Oregon)`
81 | - Series: `N1`
82 | - Machine type: `n1-standard-4 (4 vCPU, 15 GB memory)`
83 | - Under `CPU platform and GPU`: Add one `NVIDIA Tesla V100` GPU
84 | - Scroll down and check `Allow HTTP traffic` and `Allow HTTPS traffic`
85 | - Set the boot disk to `Deep Learning on Linux` and `Intel® optimized Deep Learning Image: TensorFlow 2.3 m59 (with Intel® MKL-DNN/MKL and CUDA 110)`.
86 |
87 | Now, SSH into the VM under `Open in browser window`. You might have to wait a few minutes before this is possible (before that, it might hang). Now, in the terminal:
88 |
89 | - When it prompts you for driver installation when you first log in, say yes.
90 |
91 | Now run the following commands:
92 |
93 | ```
94 | git clone https://github.com/dcharatan/point2mesh-reimplementation.git
95 | cd point2mesh-reimplementation
96 |
97 | git clone --recursive -j8 git://github.com/hjwdzh/Manifold
98 | cd Manifold
99 | mkdir build
100 | cd build
101 | cmake .. -DCMAKE_BUILD_TYPE=Release
102 | make
103 |
104 | cd ~/point2mesh-reimplementation
105 | pip3 install trimesh
106 | rm -rf /opt/conda/lib/python3.7/site-packages/cloudpickle
107 | pip3 install cloudpickle==1.4.0
108 | sudo apt-get install libopenexr-dev
109 | sudo apt-get install openexr
110 | pip3 install tensorflow-graphics
111 | ```
112 |
113 | Now you can run the training script via:
114 |
115 | ```
116 | python3 -m source.script_modules.train_model
117 | ```
118 |
119 | ### Retrieving OBJs from Google Cloud
120 |
121 | ```
122 | mv results ~
123 | zip -r temp.zip results
124 | ```
125 |
126 | If using the browser-based SSH terminal, now click the cog (settings icon) and download `temp.zip`.
127 |
128 | ## Acknowledgements
129 |
130 | The files `elephant.pwn`, `hand.pwn` and `sphere.pwn` were taken from Alex Jacobson's [mesh reconstruction project](https://github.com/alecjacobson/geometry-processing-mesh-reconstruction).
131 |
--------------------------------------------------------------------------------
/data/gifs/triceratops_gif_small.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/data/gifs/triceratops_gif_small.gif
--------------------------------------------------------------------------------
/data/objs/cylinder.obj:
--------------------------------------------------------------------------------
1 | # WaveFront *.obj file (generated by Cinema 4D)
2 |
3 | v 0 -50 0
4 | v 0 50 0
5 | v -50 -50 0
6 | v -50 50 0
7 | v -46.19397735595703 -50 19.1341724395752
8 | v -46.19397735595703 50 19.1341724395752
9 | v -35.35533905029297 -50 35.35533905029297
10 | v -35.35533905029297 50 35.35533905029297
11 | v -19.1341724395752 -50 46.19397735595703
12 | v -19.1341724395752 50 46.19397735595703
13 | v 0 -50 50
14 | v 0 50 50
15 | v 19.1341724395752 -50 46.19397735595703
16 | v 19.1341724395752 50 46.19397735595703
17 | v 35.35533905029297 -50 35.35533905029297
18 | v 35.35533905029297 50 35.35533905029297
19 | v 46.19397735595703 -50 19.1341724395752
20 | v 46.19397735595703 50 19.1341724395752
21 | v 50 -50 0.00000000000001
22 | v 50 50 0.00000000000001
23 | v 46.19397735595703 -50 -19.1341724395752
24 | v 46.19397735595703 50 -19.1341724395752
25 | v 35.35533905029297 -50 -35.35533905029297
26 | v 35.35533905029297 50 -35.35533905029297
27 | v 19.1341724395752 -50 -46.19397735595703
28 | v 19.1341724395752 50 -46.19397735595703
29 | v 0.00000000000001 -50 -50
30 | v 0.00000000000001 50 -50
31 | v -19.1341724395752 -50 -46.19397735595703
32 | v -19.1341724395752 50 -46.19397735595703
33 | v -35.35533905029297 -50 -35.35533905029297
34 | v -35.35533905029297 50 -35.35533905029297
35 | v -46.19397735595703 -50 -19.1341724395752
36 | v -46.19397735595703 50 -19.1341724395752
37 | # 34 vertices
38 |
39 | g Cylinder
40 | usemtl default
41 | f 5 3 1
42 | f 6 4 3
43 | f 6 2 4
44 | f 7 5 1
45 | f 8 6 5
46 | f 8 2 6
47 | f 9 7 1
48 | f 10 8 7
49 | f 10 2 8
50 | f 11 9 1
51 | f 12 10 9
52 | f 12 2 10
53 | f 13 11 1
54 | f 14 12 11
55 | f 14 2 12
56 | f 15 13 1
57 | f 16 14 13
58 | f 16 2 14
59 | f 17 15 1
60 | f 18 16 15
61 | f 18 2 16
62 | f 19 17 1
63 | f 20 18 17
64 | f 20 2 18
65 | f 21 19 1
66 | f 22 20 19
67 | f 22 2 20
68 | f 23 21 1
69 | f 24 22 21
70 | f 24 2 22
71 | f 25 23 1
72 | f 26 24 23
73 | f 26 2 24
74 | f 27 25 1
75 | f 28 26 25
76 | f 28 2 26
77 | f 29 27 1
78 | f 30 28 27
79 | f 30 2 28
80 | f 31 29 1
81 | f 32 30 29
82 | f 32 2 30
83 | f 33 31 1
84 | f 34 32 31
85 | f 34 2 32
86 | f 3 33 1
87 | f 4 34 33
88 | f 4 2 34
89 | f 5 6 3
90 | f 7 8 5
91 | f 9 10 7
92 | f 11 12 9
93 | f 13 14 11
94 | f 15 16 13
95 | f 17 18 15
96 | f 19 20 17
97 | f 21 22 19
98 | f 23 24 21
99 | f 25 26 23
100 | f 27 28 25
101 | f 29 30 27
102 | f 31 32 29
103 | f 33 34 31
104 | f 3 4 33
105 |
106 |
--------------------------------------------------------------------------------
/data/objs/icosahedron.obj:
--------------------------------------------------------------------------------
1 | # WaveFront *.obj file (generated by Cinema 4D)
2 |
3 | v 0 52.57319641113281 -85.06521606445312
4 | v 0 -52.57319641113281 -85.06521606445312
5 | v -85.06521606445312 0 -52.57319641113281
6 | v -85.06521606445312 0 52.57319641113281
7 | v 0 52.57319641113281 85.06521606445312
8 | v 0 -52.57319641113281 85.06521606445312
9 | v 85.06521606445312 0 52.57319641113281
10 | v 85.06521606445312 0 -52.57319641113281
11 | v -52.57319641113281 85.06521606445312 0
12 | v 52.57319641113281 85.06521606445312 0
13 | v 52.57319641113281 -85.06521606445312 0
14 | v -52.57319641113281 -85.06521606445312 0
15 | # 12 vertices
16 |
17 | g Platonic
18 | usemtl default
19 | f 1 9 10
20 | f 1 3 9
21 | f 3 4 9
22 | f 4 5 9
23 | f 5 10 9
24 | f 7 10 5
25 | f 8 10 7
26 | f 8 1 10
27 | f 2 11 12
28 | f 2 12 3
29 | f 12 4 3
30 | f 12 6 4
31 | f 12 11 6
32 | f 11 7 6
33 | f 11 8 7
34 | f 11 2 8
35 | f 1 8 2
36 | f 1 2 3
37 | f 4 6 5
38 | f 6 7 5
39 |
40 |
--------------------------------------------------------------------------------
/data/objs/remeshed_elephant_hull.obj:
--------------------------------------------------------------------------------
1 | # https://github.com/mikedh/trimesh
2 | v 0.20508160 -0.05028716 0.11785877
3 | v -0.27776255 0.22682678 -0.23598986
4 | v 0.03357697 0.20551651 0.27511320
5 | v -0.06796031 -0.28694344 -0.27614924
6 | v -0.27547647 0.38460647 -0.28479177
7 | v -0.23933439 0.10221652 0.38542411
8 | v -0.28715007 0.25067055 -0.02190969
9 | v 0.01899265 -0.17526894 -0.30009222
10 | v -0.09816062 -0.35870955 0.25919185
11 | v -0.26167806 0.08029003 0.24774497
12 | v 0.03270146 -0.32375876 0.22531945
13 | v -0.09148925 0.50739543 -0.03929118
14 | v -0.13853181 0.44716900 0.28839993
15 | v -0.19027965 -0.11484403 0.32599679
16 | v 0.03258883 0.47382838 0.08359488
17 | v 0.00733188 -0.49101744 0.15783122
18 | v 0.04472324 0.09478265 0.29269839
19 | v 0.03034116 0.46482769 -0.08381008
20 | v -0.26137595 0.03161169 -0.11575180
21 | v 0.03366405 0.47882629 -0.03827316
22 | v -0.12967930 0.49465585 -0.15156331
23 | v -0.20306426 -0.34324209 -0.09910689
24 | v -0.23035130 -0.16227816 0.14762657
25 | v 0.22583302 -0.06794984 -0.06283722
26 | v -0.16467962 0.20133116 0.43429715
27 | v -0.10233338 -0.50490029 -0.17038707
28 | v -0.00860154 0.45166569 0.15753814
29 | v -0.23787001 0.00225219 0.31591780
30 | v -0.23900409 0.23286909 -0.43445729
31 | v -0.22442233 0.28422918 -0.44036201
32 | v 0.10275591 0.14635722 -0.21522102
33 | v -0.22728440 -0.14510003 -0.18849529
34 | v 0.12072968 -0.22229933 0.14805650
35 | v 0.26909960 0.24616755 0.07565186
36 | v 0.01099528 0.36481007 -0.21230963
37 | v -0.02203383 0.37329259 -0.23523446
38 | v -0.17245431 0.48775835 0.21790675
39 | v -0.23731703 -0.14711129 0.11783322
40 | v 0.03949066 -0.09581628 -0.31288490
41 | v -0.24933878 0.15466629 -0.38225547
42 | v -0.23932918 -0.18063319 0.03694717
43 | v 0.11924747 -0.18798044 0.17299975
44 | v 0.00987404 -0.29549272 0.26382259
45 | v -0.28502772 0.24464058 0.09078105
46 | v -0.06527988 0.50996402 -0.04003026
47 | v -0.27288052 0.11015089 0.00865877
48 | v -0.00402164 0.02021066 -0.33223364
49 | v -0.18551284 -0.43617901 -0.00924513
50 | v -0.02484211 -0.42464942 0.21807279
51 | v -0.08022003 0.12171612 -0.36962465
52 | v 0.00125453 -0.09377062 0.34566986
53 | v -0.04179442 -0.46574239 0.20505850
54 | v 0.05430286 -0.42025369 0.12499413
55 | v 0.05264655 -0.42671764 -0.10854562
56 | v -0.23937777 -0.20436415 0.01689830
57 | v -0.08878831 -0.49960981 -0.17661811
58 | v -0.21842415 -0.09499762 -0.28765735
59 | v 0.16304657 0.03392654 -0.18882652
60 | v 0.07196177 0.00783792 -0.27369541
61 | v -0.12788463 -0.03664095 -0.36817537
62 | v -0.07935983 -0.27873610 0.29683070
63 | v -0.01047534 0.49959055 0.06420896
64 | v 0.11670290 -0.09767528 0.23627421
65 | v -0.15774190 0.48974010 0.02830380
66 | v -0.28553710 0.30607211 0.18479668
67 | v -0.19372526 -0.41193879 -0.05906914
68 | v -0.26313198 0.05600280 -0.10539380
69 | v -0.15629806 0.36995689 -0.35988090
70 | v -0.26965230 0.40879749 0.29309746
71 | v -0.23478254 0.42084560 -0.35500164
72 | v 0.03649043 -0.17765460 0.30616444
73 | v -0.09970925 -0.19765828 -0.31229638
74 | v -0.24154860 -0.00345143 -0.28108002
75 | v -0.15503852 -0.50058112 -0.15439388
76 | v -0.08184555 0.22655269 0.36377814
77 | v -0.27555819 0.24805530 -0.36443598
78 | v -0.05317611 -0.13710217 -0.34210532
79 | v -0.07961322 -0.50896659 0.17964056
80 | v 0.24480790 0.31368353 -0.03538647
81 | v 0.24302358 0.01005690 0.08175835
82 | v 0.12440342 -0.03318542 -0.23011006
83 | v -0.18863593 -0.25086316 -0.24528864
84 | v -0.08440297 -0.43978091 -0.20502060
85 | v -0.06536937 0.24265767 -0.32799898
86 | v 0.07372823 -0.28907237 0.18043719
87 | v 0.15892759 0.29652361 -0.12889311
88 | v -0.14973417 0.01441052 -0.38074543
89 | v 0.31116567 0.16414359 -0.00485926
90 | v 0.24049687 0.33146864 0.00078773
91 | v -0.26798056 0.27699920 -0.40283138
92 | v -0.24236264 -0.04806089 -0.21147874
93 | v -0.22528488 -0.00873848 -0.32788442
94 | v -0.17852904 -0.44421762 -0.14490434
95 | v 0.20368920 -0.16597816 -0.02666578
96 | v -0.13697512 -0.50938919 -0.15170801
97 | v 0.25377049 0.02663345 0.07128331
98 | v 0.18961433 0.20573881 -0.13022492
99 | v -0.07639783 0.50323739 0.10640749
100 | v -0.19669735 -0.26802464 -0.22384867
101 | v 0.00057113 0.49094426 0.08352129
102 | v -0.22551527 0.24237176 0.45774926
103 | v -0.21690342 0.18263992 -0.42347331
104 | v -0.25678822 0.31969648 -0.41393818
105 | v -0.20369126 0.25669160 -0.43483049
106 | v -0.08613862 0.18049356 0.37820764
107 | v -0.26098918 0.00877642 -0.02340972
108 | v 0.00006683 -0.23008467 0.30023525
109 | v 0.11612699 -0.10294383 -0.21766147
110 | v 0.29572379 0.20068272 -0.04064740
111 | v -0.28681445 0.30001999 -0.08502775
112 | v 0.18464283 -0.07298245 0.14255370
113 | v -0.15281125 0.20662210 -0.40782377
114 | v -0.20159204 0.38961355 0.40560940
115 | v -0.24927972 -0.09477515 -0.07320045
116 | v -0.20007395 0.45456259 0.33148763
117 | v -0.24986851 0.26916651 -0.43246977
118 | v -0.07345379 0.37905727 -0.27956855
119 | v 0.28184094 0.28221727 0.02227394
120 | v -0.28493282 0.28258579 0.21506118
121 | v -0.25669602 -0.01550795 0.03119027
122 | v -0.13595362 -0.15636246 -0.31430368
123 | v -0.11360226 0.48391875 -0.17746849
124 | v -0.07485666 -0.36151995 0.25876960
125 | v -0.20128481 0.21602432 -0.43202868
126 | v -0.21920736 0.06035442 0.38763311
127 | v 0.03597344 -0.14273383 0.32347221
128 | v 0.05651233 -0.37543526 0.15231271
129 | v -0.04362719 0.47810914 0.14722034
130 | v -0.22292191 0.44178803 0.35012635
131 | v -0.22020831 0.32952570 -0.42762796
132 | v -0.20183006 0.45785277 -0.30784278
133 | v -0.23326430 0.46827741 -0.19013017
134 | v -0.27157214 0.38493662 0.33767775
135 | v -0.26886366 0.12905191 0.16591371
136 | v -0.26217438 0.33080163 0.41769223
137 | v -0.27160029 0.30458479 0.40450323
138 | v 0.11648803 -0.31957613 0.08268407
139 | v -0.19752412 -0.38996545 0.04487847
140 | v -0.25516508 -0.01509959 -0.09810628
141 | v -0.11752156 -0.38131243 -0.22681248
142 | v -0.00804828 0.36797205 0.24368696
143 | v -0.02551515 0.06677343 -0.33844166
144 | v 0.15407397 -0.25139587 0.02752045
145 | v -0.28339419 0.33466631 -0.06904582
146 | v -0.18518490 0.11769379 0.42598705
147 | v -0.24882137 0.17768136 0.41332029
148 | v -0.27153625 0.15840484 -0.21237942
149 | v -0.22789848 -0.24026124 0.06188692
150 | v -0.19011288 0.26321589 0.44354506
151 | v -0.17228759 -0.25388647 -0.25713711
152 | v -0.25048791 0.01330632 -0.23108368
153 | v -0.25080790 -0.08594410 0.06409185
154 | v -0.24227798 0.20743830 0.43833684
155 | v 0.27689514 0.24684065 -0.05026096
156 | v 0.12881450 0.29927406 0.17085831
157 | v 0.13996074 0.11615241 -0.19232978
158 | v 0.21430571 -0.14812106 0.03463534
159 | v 0.14631211 -0.27006138 0.06041407
160 | v -0.16672725 0.49108362 0.18972086
161 | v 0.05512492 -0.23738401 0.24461244
162 | v 0.10713132 0.41687008 -0.05937420
163 | v -0.28730068 0.27097277 0.08844306
164 | v 0.04169260 0.04520277 -0.28910265
165 | v -0.28456660 0.29626778 -0.16202071
166 | v 0.21420844 0.03151262 0.13319759
167 | v -0.13289939 -0.01238065 0.39294556
168 | v 0.00545581 -0.36842204 -0.21179433
169 | v -0.04532691 0.38254377 -0.24860404
170 | v -0.26147538 0.06280567 0.18049744
171 | v 0.06043950 0.37797348 -0.14894032
172 | v -0.17970898 0.44503680 0.32994639
173 | v -0.02890962 0.02180884 -0.34771330
174 | v -0.19405522 -0.41025027 0.02735706
175 | v -0.17523153 -0.47950496 -0.00041837
176 | v -0.00481233 0.49415005 -0.06216711
177 | v -0.28052177 0.24351843 0.20860494
178 | v 0.02896944 -0.33812135 -0.20534580
179 | v -0.06996688 0.41324812 -0.24007291
180 | v -0.25075920 -0.01171217 -0.19261920
181 | v 0.26771760 0.14277619 0.09027318
182 | v -0.25001168 0.04357535 -0.27929844
183 | v 0.06270256 -0.07089799 -0.29844947
184 | v 0.03070768 -0.42422228 0.16131228
185 | v 0.19351601 0.15267925 0.15869870
186 | v 0.29511729 0.10201524 0.03260222
187 | v -0.16808143 -0.29097424 0.26046698
188 | v 0.03339313 -0.46892551 -0.10161733
189 | v 0.26999857 0.01400413 0.04232692
190 | v -0.12855758 -0.51060529 0.16446140
191 | v -0.04047565 0.50737497 -0.05324066
192 | v -0.17375181 -0.03113286 -0.35135569
193 | v 0.07921972 -0.08874182 -0.27904866
194 | v -0.24944843 0.04067244 0.29268171
195 | v -0.06522827 -0.19855082 0.33346270
196 | v -0.23224522 -0.24742815 0.00249026
197 | v -0.08608458 0.29274245 0.35235578
198 | v 0.05765174 -0.17246872 -0.26725133
199 | v -0.27309513 0.18078947 -0.26173635
200 | v -0.24341961 0.45999971 -0.24791048
201 | v -0.11620297 -0.07743115 -0.35638468
202 | v -0.21696713 -0.19518074 -0.20281991
203 | v 0.12651320 0.39325350 0.08686473
204 | v -0.08694216 -0.07882460 0.37590389
205 | v -0.12342216 0.15136288 -0.39473213
206 | v -0.14944456 0.06282969 0.41224356
207 | v 0.02055993 -0.49057191 -0.11239324
208 | v 0.01616953 -0.33234640 0.24005982
209 | v 0.28556345 0.07761136 -0.02391169
210 | v 0.07460409 0.07826106 -0.25441641
211 | v 0.19950650 0.04754929 0.16297287
212 | v -0.15611590 0.30637042 0.40550876
213 | v -0.24720582 0.30997005 -0.42741988
214 | v 0.04197947 0.35698117 0.20677879
215 | v -0.00127429 0.49625091 -0.04008691
216 | v -0.02869445 -0.47152956 -0.17928460
217 | v -0.22622149 -0.07001719 0.29902754
218 | v 0.30003259 0.16268381 -0.03424597
219 | v 0.14890300 -0.09698377 0.18536365
220 | v -0.20874692 -0.31603132 -0.09802227
221 | v 0.01807420 0.28643495 -0.25054097
222 | v -0.26155972 0.03222578 0.04361971
223 | v 0.27526717 -0.00578054 0.00043490
224 | v -0.27701955 0.23037235 0.26221277
225 | v -0.09023939 0.50465435 -0.08240233
226 | v -0.08620483 0.12607928 0.39235705
227 | v -0.05390275 0.13095304 0.36393516
228 | v 0.26790973 -0.03473216 0.00228850
229 | v 0.25110078 -0.08511839 0.00620397
230 | v -0.22468290 -0.03375539 0.32907373
231 | v -0.03067330 0.44208392 -0.17159355
232 | v -0.20694210 0.22037090 0.45175212
233 | v -0.23901378 -0.18741924 0.05188393
234 | v 0.09457982 -0.35611205 -0.03407771
235 | v -0.09404098 -0.13359442 -0.34215596
236 | v -0.22694082 -0.25335946 -0.03712315
237 | v -0.04075202 -0.13835463 0.35574850
238 | v -0.15534274 -0.36516010 0.23447974
239 | v -0.21952961 -0.20663947 0.17195993
240 | v -0.27255233 0.16670629 0.23974219
241 | v -0.28040641 0.29892217 0.36469164
242 | v -0.21541569 -0.15627392 0.26678601
243 | v 0.24781120 -0.06532295 -0.03042666
244 | v -0.24955584 -0.01380329 0.21467836
245 | v -0.06834880 -0.10404852 -0.35104880
246 | v -0.19203520 0.12579974 -0.40956518
247 | v 0.03089985 -0.35510270 0.20713954
248 | v 0.07172673 -0.40143077 0.09970727
249 | v 0.03343426 -0.38600723 0.18287257
250 | v -0.02595006 -0.30719560 -0.25690488
251 | v -0.17799102 0.25138819 -0.41739374
252 | v 0.15380546 0.08029246 0.20879557
253 | v 0.04111682 -0.45035692 0.07418937
254 | v -0.25900990 0.41060617 -0.32682072
255 | v -0.23528894 0.35176581 -0.41547009
256 | v -0.01836478 -0.03425128 -0.34102449
257 | v 0.14010179 -0.09616918 -0.18378071
258 | v -0.25890749 0.09722974 -0.28036021
259 | v 0.09774404 -0.20400913 -0.18216120
260 | v -0.11888828 0.46305310 -0.22483997
261 | v -0.19671740 -0.36742785 -0.11921560
262 | v -0.01220027 -0.50571016 -0.14841711
263 | v -0.08839098 0.05841378 -0.37605061
264 | v 0.23078214 0.00878482 -0.08208994
265 | v -0.11786682 0.09650421 0.40829137
266 | v -0.21868986 -0.28369714 -0.06305636
267 | v 0.23099206 0.33491689 0.03587271
268 | v -0.11943098 -0.49098587 0.19527959
269 | v -0.22760377 0.12208073 0.40999295
270 | v -0.20528825 0.36003803 -0.40703113
271 | v 0.19216200 0.01366067 -0.14295999
272 | v -0.28031928 0.21020465 0.06366644
273 | v 0.21382728 0.34792546 -0.01949359
274 | v 0.20993864 0.11480858 -0.13493690
275 | v -0.21251768 0.02789199 -0.35927690
276 | v -0.20593336 -0.29313457 0.15960604
277 | v 0.09882440 0.17411268 0.22970466
278 | v 0.09937993 0.42893468 -0.04567307
279 | v -0.16783287 -0.39869087 0.20945048
280 | v -0.20086198 0.19825415 0.44691045
281 | v -0.19723445 -0.37814277 0.10665198
282 | v -0.03204021 -0.50641486 -0.16060662
283 | v -0.15583157 0.03623556 0.40407554
284 | v 0.01733963 0.32141881 -0.23733244
285 | v -0.20281525 -0.21505936 0.25908394
286 | v -0.27938485 0.22181731 0.13720130
287 | v 0.03266123 -0.45330437 0.13975362
288 | v -0.19180212 -0.29157293 0.23299715
289 | v 0.05748556 -0.31965184 -0.17201441
290 | v 0.16894284 0.13121315 -0.16586327
291 | v -0.04257715 -0.29627685 -0.26687932
292 | v 0.00994381 -0.50468955 -0.05661275
293 | v 0.24010573 -0.09884697 -0.01430228
294 | v 0.09024333 0.41796884 -0.07833184
295 | v -0.12011443 0.40213385 0.31797394
296 | v 0.03544142 -0.45711962 -0.11730427
297 | v 0.12065870 -0.25431891 0.12725768
298 | v -0.10330802 0.50534885 0.08312350
299 | v -0.22963634 0.37258341 0.42067161
300 | v -0.10685362 0.11114728 -0.38712709
301 | v 0.09527887 -0.10150778 -0.24971732
302 | v -0.26708913 0.14069599 -0.28332190
303 | v -0.10069168 -0.02297724 -0.37025762
304 | v 0.10789456 -0.16107271 0.20924210
305 | v 0.14580305 0.22588513 -0.16109639
306 | v -0.27163311 0.36459796 -0.34333015
307 | v -0.21394095 0.43853166 -0.34103202
308 | v -0.18096559 -0.18485883 0.30005381
309 | v 0.00870482 0.20805645 -0.27637238
310 | v -0.27405230 0.17539603 -0.16137656
311 | v -0.20521134 -0.34498381 0.09491059
312 | v -0.22549678 -0.22940026 -0.08051646
313 | v 0.16793683 -0.17689597 0.10105447
314 | v 0.18472019 -0.11647037 0.11379369
315 | v -0.24370860 0.45952449 0.02870817
316 | v -0.27299500 0.12563352 -0.04068257
317 | v 0.07262804 -0.10334774 0.30142609
318 | v -0.25690796 0.00403317 0.12050933
319 | v 0.16584277 0.04585771 0.20286267
320 | v -0.13881259 0.48420598 0.21600199
321 | v 0.25386837 0.30206047 0.05599640
322 | v 0.22249301 -0.01357695 0.10438729
323 | v 0.06339157 0.35011921 -0.17598596
324 | v 0.25091669 0.28320016 -0.05686359
325 | v -0.23726251 -0.13333842 -0.12469573
326 | v -0.02526634 0.01382251 0.36333464
327 | v -0.11408042 0.33708654 0.35802986
328 | v -0.23849129 -0.03458501 0.27287357
329 | v 0.03287639 -0.46418245 -0.06166448
330 | v 0.26866519 0.27636170 0.06193022
331 | v -0.16320169 0.49118064 -0.16978350
332 | v -0.26964648 0.24813763 0.40476359
333 | v 0.15117912 0.32149772 0.13682647
334 | v -0.07660711 -0.14021598 -0.34395895
335 | v 0.00882009 -0.14500446 -0.31923513
336 | v -0.14607270 0.15283282 0.42440678
337 | v 0.10435418 -0.02864575 0.27105573
338 | v -0.24035750 -0.19708171 -0.00872268
339 | v 0.23172442 0.26102360 0.10161202
340 | v 0.22269274 0.07249100 -0.11921612
341 | v 0.06290522 0.31118609 -0.20384666
342 | v 0.08344411 -0.36469143 -0.10074047
343 | v -0.27296421 0.14769832 -0.14068937
344 | v -0.19362213 -0.25787723 0.25149138
345 | v -0.14902181 0.49309584 -0.08462741
346 | v 0.04641371 -0.12687084 -0.30436309
347 | v 0.00383323 -0.19623747 0.31411801
348 | v 0.03486812 0.01662222 0.31924414
349 | v -0.00042916 -0.50935990 -0.10567336
350 | v -0.22777764 0.38975061 0.40903761
351 | v -0.13859236 0.30371029 -0.37336639
352 | v -0.27687332 0.23149406 -0.26754791
353 | v -0.21475491 0.43253625 0.36652133
354 | v -0.27038627 0.40689912 -0.24571981
355 | v -0.26055075 0.14688185 0.35201619
356 | v 0.09035357 -0.24660656 0.18168271
357 | v -0.07105186 0.50990770 0.06073738
358 | v -0.21585314 -0.21760774 0.18916064
359 | v -0.11282082 0.16598509 -0.38542914
360 | v -0.21435810 -0.22750730 -0.17193588
361 | v -0.26057377 0.05194141 0.21160872
362 | v -0.10523809 0.49867397 0.14106837
363 | v -0.27166113 0.12968984 -0.08563392
364 | v -0.23188128 0.38921811 -0.39054844
365 | v -0.20158112 -0.37590041 -0.06396999
366 | v -0.10822560 -0.27572723 -0.27561738
367 | v 0.04054368 0.44023744 -0.10432248
368 | v -0.23994528 -0.16106309 0.08296084
369 | v 0.07731552 -0.36278151 0.12718010
370 | v -0.27197856 0.15703591 0.20091233
371 | v -0.23442335 -0.00868285 -0.30511428
372 | v -0.01106078 -0.08670367 -0.33425267
373 | v -0.28095390 0.22202900 -0.08020264
374 | v 0.13587042 0.30746212 -0.14286246
375 | v -0.27896990 0.21813367 -0.12903826
376 | v -0.16873630 0.07643681 0.41518937
377 | v 0.01812578 -0.49453948 0.12640637
378 | v -0.14302110 0.47258894 0.24747533
379 | v 0.13825122 0.33651555 -0.11747598
380 | v -0.05570205 0.46055468 -0.17028831
381 | v -0.24438429 0.27640471 0.45391998
382 | v -0.21840541 -0.15310156 -0.24534135
383 | v -0.26275228 0.03964220 -0.05375032
384 | v -0.21086108 -0.33596471 -0.04948515
385 | v -0.24616348 0.20197569 -0.41427085
386 | v 0.13018468 0.04834968 0.23533915
387 | v -0.28200348 0.27659907 0.33367908
388 | v 0.19824486 0.00972204 0.14986708
389 | v 0.28876646 0.15660381 0.06508770
390 | v 0.25012838 0.17048969 -0.08951422
391 | v -0.23871386 -0.09180217 0.18997093
392 | v -0.09579418 -0.25671991 0.30512948
393 | v 0.08013927 -0.11640637 0.28318636
394 | v 0.01995623 -0.00669332 0.33762336
395 | v 0.07051623 0.45155563 0.01483852
396 | v -0.28533401 0.26821321 -0.15633010
397 | v -0.22882201 0.09306370 -0.37707521
398 | v -0.14491801 0.15881416 -0.40575848
399 | v -0.07469976 0.29635160 -0.32313422
400 | v 0.17709416 0.02934497 0.18846790
401 | v -0.25858200 0.22442956 0.42224311
402 | v -0.10192536 0.33001138 -0.33342761
403 | v 0.01342824 -0.37316355 0.22016098
404 | v -0.19534232 -0.07930338 -0.32142935
405 | v -0.16380823 -0.33938518 -0.22409003
406 | v 0.26093417 -0.02573568 0.04206586
407 | v -0.17618839 0.17912893 -0.41993990
408 | v 0.05844841 -0.09471773 0.32086128
409 | v -0.25873303 0.38452437 -0.36403605
410 | v -0.11509655 -0.02563781 0.39024883
411 | v -0.16555415 0.49014186 0.14716152
412 | v -0.24470167 0.39420548 0.39391375
413 | v -0.21879958 0.36346792 0.42896026
414 | v -0.03516054 -0.35449729 -0.23773238
415 | v -0.12807318 -0.49238473 -0.17492731
416 | v 0.18721898 -0.04945866 -0.12719239
417 | v -0.03443861 -0.19972962 0.32703403
418 | v -0.21127573 0.26001679 0.45731381
419 | v 0.08627051 0.00377674 0.28178119
420 | v -0.27627924 0.37959973 0.30469870
421 | v 0.06061932 0.20331321 -0.23560470
422 | v 0.14526316 -0.01345786 0.22263096
423 | v -0.04926612 0.45286622 0.19549342
424 | v -0.16882323 -0.50375879 0.12302472
425 | v 0.28030317 0.03487359 0.02704908
426 | v -0.06236051 -0.24293687 -0.29605894
427 | v -0.23755922 -0.11740170 0.15836059
428 | v -0.08939922 -0.10409797 -0.35344634
429 | v -0.10446994 0.36755618 -0.31611068
430 | v -0.05726866 -0.13681186 0.36071756
431 | v -0.21567890 -0.31519331 0.04490899
432 | v 0.17102190 0.36564572 -0.05322747
433 | v -0.21153169 0.41635698 -0.36840972
434 | v -0.02676880 0.19880339 0.32578029
435 | v -0.12780432 0.19043916 0.41080054
436 | v 0.10500209 0.04269042 -0.23920593
437 | v -0.23784590 -0.06366437 0.23397622
438 | v -0.28566158 0.27942683 -0.19658584
439 | v 0.02962802 0.47558987 -0.06462142
440 | v -0.22767245 -0.09417104 -0.25805429
441 | v 0.30090612 0.19448333 0.05091303
442 | v 0.22346356 -0.12042201 -0.03361654
443 | v 0.26939765 -0.01143704 0.02715468
444 | v -0.26184331 0.05126643 -0.14676503
445 | v -0.28145035 0.32451553 -0.31222057
446 | v -0.10174859 -0.06044316 -0.36440674
447 | v 0.10547061 -0.33979375 -0.07219774
448 | v -0.24119435 -0.16571571 -0.04982184
449 | v -0.22133898 0.32202700 0.44888765
450 | v -0.18698634 0.21302827 0.44467026
451 | v -0.25118090 0.09754674 0.34885495
452 | v -0.27272330 0.19984493 0.29386555
453 | v -0.22740105 -0.26242507 0.03656813
454 | v -0.17885833 0.14790958 0.43349842
455 | v -0.20762249 -0.20766604 -0.23788956
456 | v 0.11100023 -0.07339989 0.25295189
457 | v 0.03958903 0.47463621 0.02299401
458 | v 0.14519159 0.39827972 0.04857316
459 | v -0.02382985 -0.14906469 -0.33011324
460 | v -0.08639368 -0.18980135 0.33705576
461 | v 0.29372792 0.23994018 -0.02770589
462 | v 0.11130232 -0.12125131 0.22982694
463 | v -0.00919944 -0.36014338 0.24221414
464 | v -0.06013831 0.01880048 -0.36155686
465 | v -0.26716039 0.20334138 -0.36148228
466 | v -0.16209543 0.42057116 -0.32444765
467 | v -0.23411591 0.31967208 -0.43087054
468 | v 0.01443450 -0.03564855 -0.32588939
469 | v -0.28628102 0.26056189 -0.09158315
470 | v -0.23772294 0.34601543 0.43432387
471 | v 0.17589370 -0.20550555 0.06869498
472 | v -0.28118918 0.21589080 -0.03993658
473 | v -0.02616709 -0.07506436 0.35776587
474 | v 0.23665275 0.12012540 -0.10703775
475 | v -0.23234181 -0.20823551 -0.06178141
476 | v 0.20812667 0.06957192 -0.13844815
477 | v 0.01045883 -0.14888658 0.33432889
478 | v 0.08878460 0.05304282 0.26694116
479 | v 0.13215100 -0.28578508 0.08616848
480 | v -0.26194307 0.03635774 0.13247478
481 | v -0.16683395 0.44093292 -0.30317467
482 | v -0.03966339 -0.21451772 -0.30433437
483 | v -0.28148867 0.32975532 0.32657464
484 | v -0.26248067 0.40266930 0.35017366
485 | v -0.09207939 0.49311148 -0.13347009
486 | v -0.06447747 0.38568681 0.28058497
487 | v -0.26403714 0.24483442 -0.40612628
488 | v 0.04861054 -0.27280677 0.23158904
489 | v -0.08688770 -0.21733815 0.32493673
490 | v -0.04144514 0.35321472 0.28771013
491 | v 0.24852351 0.10684385 0.10872374
492 | v -0.04551297 0.32766263 -0.28724639
493 | v -0.12739713 -0.22617159 -0.28838222
494 | v -0.07788160 0.09688821 -0.37143377
495 | v -0.17652608 0.07752889 -0.39636735
496 | v -0.28459392 0.26252413 0.17881786
497 | v 0.06556033 0.23610900 0.24176102
498 | v -0.25530751 0.12768274 -0.34544139
499 | v -0.24937087 0.01570613 0.25496844
500 | v -0.08758142 -0.49823616 0.19487418
501 | v -0.09268350 -0.29783523 -0.27004203
502 | v 0.06403202 -0.25490782 0.21868828
503 | v -0.28389502 0.22918207 0.03625237
504 | v -0.00143486 -0.26925683 0.28244679
505 | v 0.27469443 0.15525275 -0.06558766
506 | v -0.19830397 -0.15231974 0.30206876
507 | v 0.15104899 0.06739383 -0.19571778
508 | v 0.30901123 0.15558750 0.02577748
509 | v 0.29527939 0.08513976 0.00231445
510 | v 0.15904387 -0.24541193 -0.04617671
511 | v -0.19400829 -0.40918138 0.09322853
512 | v -0.27250556 0.15774132 0.13543040
513 | v 0.01989764 -0.24211788 -0.26655594
514 | v -0.27425404 0.34322570 0.37471749
515 | v 0.11574916 -0.31906679 -0.02232663
516 | v -0.04670331 0.50818972 0.01129169
517 | v 0.13029765 0.00002298 0.24215402
518 | v 0.26218630 -0.06066953 -0.00186262
519 | v -0.11463034 -0.01315990 -0.37614315
520 | v -0.19039866 0.47100135 -0.27275100
521 | v -0.23753850 0.44363602 0.33173948
522 | v -0.26108026 0.01447068 0.04474625
523 | v -0.23223162 -0.05131808 -0.27917328
524 | v -0.07356538 0.49596204 0.13275621
525 | v -0.19598202 -0.34400393 -0.15946341
526 | v -0.28303225 0.33458011 0.15344283
527 | v 0.20720263 0.34153518 0.06140588
528 | v 0.05855623 0.27449953 -0.22039101
529 | v 0.20475271 -0.16589014 0.04096102
530 | v -0.19696249 -0.31149372 -0.19177036
531 | v 0.06727815 0.44309045 -0.07243706
532 | v 0.12776839 -0.30209757 -0.05922358
533 | v -0.20443768 0.48104603 -0.13268899
534 | v -0.10796920 -0.33932890 0.26526442
535 | v -0.02126200 -0.37335341 -0.22322347
536 | v -0.11366264 0.27497723 0.37807672
537 | v -0.00283512 0.25606712 0.29261837
538 | v 0.26325128 0.10876642 -0.07122169
539 | v -0.17967480 0.48437693 0.09844032
540 | v 0.01313673 -0.44658963 -0.16059644
541 | v -0.20693111 -0.26830523 -0.17988644
542 | v 0.05597577 -0.42441111 0.06831314
543 | v 0.05931145 -0.42232700 0.10523981
544 | v 0.25601673 0.31538177 0.02336052
545 | v -0.21248375 0.47996229 -0.18010430
546 | v -0.22447990 0.20503428 0.44578271
547 | v 0.14263434 -0.16601950 0.14946781
548 | v -0.12402806 0.01211354 0.39899260
549 | v -0.17905014 0.48290594 0.24854421
550 | v 0.22008202 -0.06677168 0.08961825
551 | v 0.28916875 0.19264620 0.06620253
552 | v -0.08823413 -0.04461853 0.38304572
553 | v 0.04900747 0.27990492 0.24540865
554 | v 0.09375372 0.32606272 0.18501827
555 | v 0.13835660 -0.17985963 -0.13241251
556 | v 0.00802444 0.15265056 0.30857275
557 | v -0.00561524 -0.25265046 -0.27451458
558 | v -0.26116212 0.12121834 0.31085784
559 | v -0.22157940 -0.29206547 -0.02612666
560 | v -0.27241587 0.17682954 0.28153424
561 | v 0.05876109 0.32114157 0.21987532
562 | v 0.08963981 0.34441054 0.17281042
563 | v -0.04138611 -0.49236616 0.19024441
564 | v -0.18942323 -0.41151188 -0.11469823
565 | v -0.11351410 0.28574851 -0.35682714
566 | v -0.25111922 -0.08872527 -0.01876860
567 | v 0.17285007 0.01703303 -0.17602288
568 | v -0.15810417 0.31514159 -0.38544683
569 | v -0.00641904 -0.44937255 -0.17820785
570 | v 0.02176912 -0.11052620 0.33674247
571 | v -0.10988656 -0.31598229 -0.25765662
572 | v -0.26112624 0.19665510 -0.38199524
573 | v 0.20878992 0.34225496 -0.03949528
574 | v 0.21424784 0.17477235 -0.11879739
575 | v -0.17040752 -0.49413684 -0.13286714
576 | v -0.21421423 0.40323835 0.39885119
577 | v 0.07102609 -0.21860662 0.23025071
578 | v 0.26069905 0.08500226 -0.06453652
579 | v -0.23811694 0.31653800 0.44808727
580 | v -0.23350638 0.46834854 0.03445882
581 | v -0.20458094 0.28450312 -0.43119409
582 | v -0.27273582 0.19204033 0.32080354
583 | v -0.23734382 0.25372962 0.45709846
584 | v 0.05590675 -0.16251958 0.29365351
585 | v 0.21516947 -0.12289153 0.06030904
586 | v 0.19867540 -0.08922717 0.10896566
587 | v -0.12440172 0.49664114 -0.02376211
588 | v -0.22744684 -0.16829104 0.17050623
589 | v -0.18666347 -0.39291127 -0.16042036
590 | v -0.22790763 -0.08784627 0.27491437
591 | v -0.23392106 0.26822952 -0.44153867
592 | v 0.00671382 -0.50645985 0.13114499
593 | v -0.21524586 0.31436054 -0.43253423
594 | v -0.08217194 0.48179949 0.17581784
595 | v -0.25044586 -0.07782630 0.10240790
596 | v 0.24596119 -0.05496560 0.05459537
597 | v -0.25472617 0.44508099 0.27702944
598 | v -0.19883625 -0.17782162 -0.27236622
599 | v -0.26349929 0.42802107 -0.26028652
600 | v -0.04843368 -0.45262124 -0.19570858
601 | v -0.05875560 -0.18394053 -0.32324638
602 | v -0.21331559 -0.05827122 0.33428615
603 | v -0.01207655 -0.46228749 0.19339057
604 | v 0.18718105 0.10228605 0.17593668
605 | v -0.25706343 0.43115342 -0.28958149
606 | v -0.21298790 0.02808432 0.37665257
607 | v -0.28929894 0.27895543 -0.01218124
608 | v -0.20667494 -0.23899503 0.22550937
609 | v 0.10363035 0.42226201 0.07294856
610 | v -0.03923045 0.50798623 0.06365932
611 | v -0.17147493 -0.43781888 0.18286422
612 | v -0.25849445 0.07964373 0.26269177
613 | v 0.23261339 0.06385479 0.11666758
614 | v -0.14795620 -0.43749619 -0.18957022
615 | v 0.01851292 0.39863440 -0.16843894
616 | v -0.26917988 0.41101018 0.06138221
617 | v -0.09613412 0.45171480 0.24116619
618 | v -0.01450340 -0.48810600 -0.16361696
619 | v 0.17842589 -0.19949319 -0.05471841
620 | v 0.27816349 0.03505459 -0.01993685
621 | v -0.14315619 -0.44910652 0.20461478
622 | v -0.27679843 0.21055850 -0.18546731
623 | v -0.20848995 0.34390901 0.43407477
624 | v -0.06526818 -0.42799468 0.22779106
625 | v 0.22250654 -0.13561684 -0.00920246
626 | v 0.14662558 -0.23292518 0.09908207
627 | v -0.26145633 0.04920171 0.11728982
628 | v -0.14069857 0.08003327 0.41428609
629 | v -0.04915297 -0.02180912 -0.35348448
630 | v -0.00777312 -0.41128979 -0.19857553
631 | v -0.22760800 -0.22361824 0.09177078
632 | v 0.00013217 0.46223422 -0.11548684
633 | v -0.22565215 0.46800344 0.29054159
634 | v -0.20335711 0.47188523 0.29585361
635 | v -0.28574559 0.31705672 0.08333586
636 | v -0.03932767 -0.25397958 0.30285669
637 | v -0.16314462 0.11259623 -0.40729851
638 | v 0.15339682 -0.23990287 -0.06912460
639 | v -0.26275422 0.07648245 -0.22051266
640 | v -0.23642718 0.46647040 0.25279995
641 | v -0.09481054 -0.41989677 0.23118770
642 | v 0.27429026 0.10420945 0.06829268
643 | v 0.00574114 -0.28383433 -0.25382693
644 | v -0.24121182 -0.04753771 0.22610209
645 | v -0.07732317 -0.17314898 -0.32951839
646 | v -0.27237725 0.22743650 0.37387292
647 | v -0.02522205 -0.51040586 0.15532774
648 | v -0.26215261 0.01419703 0.01055387
649 | v 0.01181860 0.10819137 -0.29816618
650 | v -0.13706076 -0.51056719 -0.12964873
651 | v -0.23589467 -0.08863165 -0.19669194
652 | v 0.08136613 -0.38413599 -0.08176043
653 | v -0.23901531 0.45567822 0.30178358
654 | v -0.22562651 0.27872656 0.46040942
655 | v -0.22748763 -0.13784450 0.21153344
656 | v -0.10491739 -0.29219082 0.28625051
657 | v 0.03864741 -0.27915336 -0.22826602
658 | v -0.04382554 -0.39084098 0.24000769
659 | v -0.20342874 -0.30539178 -0.14755895
660 | v -0.21714266 -0.29628307 0.07016290
661 | v -0.21701978 -0.18882044 0.22706549
662 | v -0.11343706 -0.05679598 0.38209478
663 | v -0.20515930 -0.27582333 0.19518111
664 | v 0.01386407 -0.41246961 0.19685204
665 | v -0.20096601 0.44717281 -0.32550462
666 | v -0.12672857 0.50027782 -0.10150801
667 | v -0.04278361 -0.49767714 -0.17197794
668 | v -0.21572952 -0.27471541 0.11072091
669 | v -0.07603288 -0.10741209 0.37104844
670 | v -0.23961943 0.03813124 -0.32687694
671 | v 0.18047913 0.08537778 -0.16676768
672 | v -0.03072854 -0.51050657 -0.13449934
673 | v 0.04075178 0.33530323 -0.20833325
674 | v -0.10144598 -0.01355568 0.39061376
675 | v 0.14956714 -0.26282447 -0.03699979
676 | v 0.06507434 0.45341645 -0.05437092
677 | v -0.05745494 0.06127853 -0.36178771
678 | v -0.01088342 -0.42392330 0.21170350
679 | v 0.10380970 0.35605960 -0.12977567
680 | v 0.01353386 0.45965136 0.12481682
681 | v -0.13172053 0.02213467 -0.38494492
682 | v 0.18168491 0.30796316 -0.10094672
683 | v -0.16472405 0.39547630 -0.35010324
684 | v -0.03141460 -0.16410109 0.34310881
685 | v -0.22720340 -0.11473427 0.24735292
686 | v -0.26255444 0.27127063 0.43415220
687 | v -0.25111124 0.07580102 -0.31386587
688 | v -0.24719699 0.36505328 0.41547636
689 | v -0.00986963 -0.50469688 0.16279099
690 | v -0.10218578 -0.09556772 0.37057386
691 | v -0.10198354 0.50494018 0.02681506
692 | v 0.08483246 -0.37167545 0.10397621
693 | v -0.14584657 0.49514214 0.10121132
694 | v -0.24121169 -0.14477463 -0.08272205
695 | v -0.11144017 0.50181535 0.05142214
696 | v -0.27528017 0.38600164 0.05404691
697 | v -0.05835601 0.04222367 0.37983287
698 | v -0.28296016 0.29629412 -0.32082097
699 | v -0.01925456 0.48302198 -0.09904971
700 | v 0.26522288 0.28374351 -0.04064546
701 | v 0.01380273 -0.41564469 -0.17767734
702 | v 0.07674033 -0.05410166 0.30094691
703 | v 0.08885425 -0.19987294 0.21421752
704 | v -0.12235854 -0.45109919 -0.19438905
705 | v -0.08421710 -0.36078005 -0.24168983
706 | v -0.26166094 0.02580901 0.08939704
707 | v -0.24641101 -0.12807361 -0.02328124
708 | v 0.21393843 0.25802124 -0.09876102
709 | v -0.17399117 -0.38021292 -0.19494828
710 | v -0.26112077 0.34659639 -0.39119771
711 | v 0.01117108 -0.44440409 0.18190431
712 | v -0.23000140 0.41781607 0.37885024
713 | v 0.09268404 -0.12953977 0.25410519
714 | v -0.01056076 -0.13731959 0.34520329
715 | v 0.27069621 0.20626300 -0.06434565
716 | v -0.18915147 -0.31617419 -0.20772608
717 | v 0.09857205 -0.28686900 -0.12708648
718 | v 0.15635637 0.15000513 0.18873882
719 | v -0.21385803 -0.10910048 0.30364937
720 | v 0.01270966 -0.50025877 0.08202675
721 | v -0.25073226 -0.02811812 0.18239441
722 | v 0.00750006 -0.33152521 -0.22922477
723 | v -0.27155786 0.13382598 0.10852365
724 | v -0.26218314 0.04743744 0.17629390
725 | v -0.12501577 0.03642699 -0.38657385
726 | v 0.16613047 -0.07951466 0.16674655
727 | v 0.13288375 0.26757120 0.18030802
728 | v 0.19315128 -0.08260039 -0.10713710
729 | v -0.28182088 0.21019942 -0.00826876
730 | v 0.29825465 0.24580863 0.02836050
731 | v -0.27149894 0.12238272 0.06142629
732 | v -0.26622278 0.11142962 -0.24694930
733 | v -0.26290758 0.14993070 -0.32816010
734 | v -0.18229062 -0.46017083 0.07223278
735 | v -0.05953093 -0.34850381 -0.24642957
736 | v -0.01824325 0.28747366 -0.27941511
737 | v -0.25141573 -0.06072285 -0.11472944
738 | v -0.19879493 0.42873987 0.36732221
739 | v 0.09745339 -0.06801469 0.27638170
740 | v -0.25069380 -0.09549940 0.02440272
741 | v 0.10996924 0.32389617 -0.15445565
742 | v -0.00143172 -0.50969388 0.12607692
743 | v -0.12810307 0.20826459 -0.38734143
744 | v -0.19548483 0.16244865 0.43781813
745 | v 0.10078397 0.43189039 -0.02096315
746 | v -0.20525635 -0.16256802 0.28802728
747 | v 0.06956222 0.13968929 0.26174128
748 | v -0.21394754 0.47718695 0.27198508
749 | v -0.27470146 0.14603428 -0.01705510
750 | v 0.12002495 -0.28796839 0.10669780
751 | v -0.13355585 0.49194896 0.18524907
752 | v 0.20421823 0.30583914 0.10089726
753 | v -0.24142145 -0.12340235 0.11711505
754 | v -0.20202049 0.48413704 -0.22216340
755 | v 0.30789567 0.20528536 -0.00717007
756 | v 0.29576896 0.11800243 -0.02263976
757 | v 0.06116032 0.44461783 0.09709456
758 | v -0.25390912 0.44692336 -0.23755181
759 | v -0.15864896 -0.50211469 0.16616348
760 | v 0.28383937 0.03313021 0.00628776
761 | v -0.12378175 0.44495415 -0.25709072
762 | v 0.05670849 -0.12835357 0.31387769
763 | v -0.28271167 0.27760402 -0.31692078
764 | v -0.28073791 0.34777206 -0.27174915
765 | v -0.25047103 -0.05258559 0.14889054
766 | v -0.28567358 0.31214946 -0.11171428
767 | v 0.15933378 -0.23819288 0.07268713
768 | v 0.03826370 -0.45899034 0.10424463
769 | v -0.18126142 -0.39036253 0.19237098
770 | v -0.10752062 -0.40270447 0.23766697
771 | v -0.22713661 0.01203096 0.35348600
772 | v -0.25340988 0.30345489 0.44061447
773 | v -0.18327100 -0.45580539 -0.09033531
774 | v -0.08233276 -0.13708821 0.36104708
775 | v -0.27752508 0.27854213 0.38235174
776 | v 0.19112388 -0.17268678 0.06697264
777 | v 0.03462596 -0.28239815 0.24888117
778 | v -0.00402492 0.09005616 0.33334087
779 | v -0.27260987 0.18193013 0.23194586
780 | v -0.23510843 0.04189319 0.35545030
781 | v -0.18288699 -0.33999913 0.22190267
782 | v 0.01181894 -0.03614074 0.34458604
783 | v -0.15526714 0.06961188 -0.39681326
784 | v 0.05192390 -0.30307224 0.20602189
785 | v -0.25043770 -0.06177556 0.09984918
786 | v -0.22954297 -0.19244662 -0.10624356
787 | v 0.04313031 -0.16354582 -0.29022096
788 | v 0.08104392 -0.15596310 0.25579110
789 | v -0.13702456 0.07474584 -0.39453312
790 | v 0.15472073 0.00482325 0.21435198
791 | v -0.26176055 0.37045826 0.38963529
792 | v -0.24599863 -0.11822269 0.03996143
793 | v 0.26208457 -0.05526933 0.01857161
794 | v -0.00062006 0.28103526 0.28578744
795 | v -0.06991670 0.43245709 0.23725891
796 | v -0.20287542 -0.01749893 0.36185147
797 | v 0.25602250 0.18485454 0.09967798
798 | v -0.15202613 -0.50847755 0.15962134
799 | v 0.17966285 -0.14257243 -0.09003206
800 | v -0.28355379 0.33241448 -0.13954292
801 | v -0.04512562 -0.34444778 0.26219597
802 | v -0.27243058 0.14094708 0.06761635
803 | v 0.13652430 0.20408522 0.19199171
804 | v 0.02730703 -0.23622620 0.28243510
805 | v -0.13650481 0.47576793 -0.21685571
806 | v -0.24772400 -0.04909364 -0.16266534
807 | v 0.29375440 0.25908590 -0.00477060
808 | v 0.16470224 -0.12800507 0.13854121
809 | v -0.22588464 0.47586120 -0.23436908
810 | v -0.24822831 0.06946170 0.33279859
811 | v 0.13390801 -0.14233586 0.17889750
812 | v -0.18262319 -0.18375317 -0.28238553
813 | v 0.13927633 -0.04446689 0.21991464
814 | v 0.08821713 0.44018991 0.05235171
815 | v 0.05288401 -0.33584812 0.18316879
816 | v 0.09708242 0.27039000 0.20858021
817 | v -0.11325998 0.39799316 -0.29929160
818 | v 0.02399186 -0.47258299 0.14084965
819 | v -0.24856110 0.14075309 0.39048589
820 | v -0.17840430 0.37899757 0.39150529
821 | v 0.15124690 -0.20707318 0.10862101
822 | v 0.12613075 0.01082024 -0.22779861
823 | v -0.09987373 0.06152666 0.39811060
824 | v -0.26163501 0.17846995 0.38019756
825 | v -0.20253997 0.48344568 0.20357566
826 | v -0.20955180 -0.33559342 0.05485950
827 | v 0.18151636 0.32634486 -0.08532037
828 | v -0.05488683 -0.00799996 0.37446764
829 | v 0.30832085 0.21260775 0.01930162
830 | v -0.02968363 0.06326309 0.36160944
831 | v 0.16037077 -0.24500726 0.05166355
832 | v 0.22795989 -0.11758161 0.03794139
833 | v 0.17094869 -0.03389182 0.17537313
834 | v -0.18282026 -0.42112558 0.16019153
835 | v 0.23283414 -0.09369084 0.05040286
836 | v -0.21930794 0.47624667 0.23781472
837 | v 0.23765974 0.22459545 -0.08756708
838 | v -0.02039850 -0.49556667 0.18046113
839 | v 0.28049366 0.05729257 0.04268218
840 | v -0.26147113 0.02191590 -0.07379946
841 | v 0.24341975 -0.01981118 0.06999440
842 | v 0.02792151 0.48131025 0.06284741
843 | v 0.19996120 -0.13137990 0.07927001
844 | v -0.05497128 -0.39714867 -0.22314904
845 | v -0.05953832 0.48418617 -0.13254210
846 | v -0.28496168 0.32292197 -0.05740177
847 | v -0.26061096 0.09850476 0.28298781
848 | v 0.13826008 0.31734685 0.14971917
849 | v 0.12858329 0.11091688 0.22068787
850 | v 0.04490969 0.13291240 -0.26522522
851 | v -0.17445159 -0.49354371 -0.11175719
852 | v -0.18747175 -0.43529294 0.09699659
853 | v -0.20908071 0.38275626 -0.39724223
854 | v -0.12246566 0.49897525 0.12388852
855 | v 0.29576410 0.13948074 0.04989131
856 | v 0.12349402 -0.29533939 -0.08103790
857 | v -0.07815206 0.08968868 0.39006172
858 | v -0.25009957 0.40882843 0.36941398
859 | v 0.13410011 0.40766366 0.01107026
860 | v 0.13939420 -0.08078759 0.20754500
861 | v -0.05509412 0.32234132 0.31537394
862 | v 0.01974749 -0.00713512 -0.31958068
863 | v 0.06436317 -0.11596410 -0.29217488
864 | v -0.21660707 -0.24311398 0.15104987
865 | v 0.26446557 -0.01182854 -0.02367746
866 | v 0.10161374 -0.34810933 0.08133041
867 | v -0.22008098 -0.22983496 -0.12997545
868 | v 0.19223518 0.28108881 0.12564231
869 | v -0.21850658 0.35213914 -0.41779854
870 | v 0.01248991 -0.50341132 -0.11166817
871 | v -0.26244385 0.42920229 0.29037302
872 | v -0.07808801 0.43925836 -0.22119358
873 | v -0.26901280 0.09820824 -0.02242808
874 | v 0.27105504 0.29622522 -0.00982454
875 | v -0.24202016 0.46121810 0.26244371
876 | v -0.14631456 0.49365837 0.16742431
877 | v 0.22268384 0.08459748 0.13949064
878 | v -0.27261983 0.15763130 -0.17432476
879 | v -0.22321951 0.24458522 -0.44026786
880 | v 0.17859297 0.26084662 0.14473574
881 | v 0.11127777 0.27174918 -0.17829584
882 | v -0.02535198 -0.24216045 -0.28735761
883 | v 0.17983202 -0.20853170 0.04367466
884 | v -0.21458718 0.16768439 0.43588834
885 | v -0.21127966 0.30083274 0.45197312
886 | v -0.19355678 -0.37842182 0.13929039
887 | v -0.20568861 -0.32330522 0.12428603
888 | v 0.20947427 0.28698327 -0.09043979
889 | v -0.16858397 -0.47618456 0.16417506
890 | v 0.25109732 -0.07656556 0.02986202
891 | v -0.02736924 0.18560380 -0.31111677
892 | v -0.24806172 0.37697713 -0.38896608
893 | v -0.05467932 0.09591440 -0.35501459
894 | v -0.27224348 0.15961893 0.16851941
895 | v -0.19410972 -0.34649622 0.18068590
896 | v 0.10802148 -0.31982863 0.10527411
897 | v -0.19721499 -0.38454665 0.00054586
898 | v -0.13742107 0.03213556 0.40563537
899 | v -0.04757275 -0.05227590 0.36747183
900 | v 0.18782693 -0.00974352 0.15861589
901 | v -0.21772059 0.47420482 -0.26937736
902 | v -0.01642267 -0.18454120 -0.31158642
903 | v -0.02615066 0.34739580 -0.25989183
904 | v -0.20561434 0.48138898 0.24584759
905 | v 0.08289510 -0.32647625 0.14081920
906 | v 0.16747494 -0.08840578 -0.14470012
907 | v 0.05769447 -0.42571084 -0.08320504
908 | v -0.21484569 0.47806296 0.11936861
909 | v -0.15126553 0.26334289 0.41120448
910 | v -0.22857241 -0.11902029 -0.21767125
911 | v -0.22784025 -0.16968138 -0.14984267
912 | v -0.28343833 0.27673565 -0.26075997
913 | v -0.27224345 0.14406239 -0.11651924
914 | v -0.20008473 -0.06657665 0.34203216
915 | v 0.28238567 0.26514398 -0.02952951
916 | v 0.15163112 -0.03762202 -0.18566481
917 | v -0.04625176 0.28832112 0.32071794
918 | v 0.18380776 0.37160723 0.03396138
919 | v -0.10121240 0.02649261 0.39535897
920 | v -0.13645592 -0.50130729 0.18428872
921 | v 0.22733800 0.13725478 0.13336740
922 | v -0.17486103 -0.22771061 0.28440231
923 | v -0.21238551 -0.27530539 -0.12319397
924 | v -0.04194327 -0.29561822 0.28452224
925 | v -0.22015510 0.46006486 0.31833692
926 | v -0.25531926 -0.02557181 -0.05006997
927 | v -0.28151318 0.31399255 -0.07703474
928 | v 0.03048682 -0.20357181 -0.27897197
929 | v -0.18431254 0.21906711 -0.42508519
930 | v 0.04306923 -0.22291618 0.27386022
931 | v 0.00901867 0.34200616 0.25013361
932 | v 0.00575979 -0.49592758 -0.13958492
933 | v -0.24093204 0.08660662 -0.35649556
934 | v 0.10281708 0.31009276 -0.16952485
935 | v 0.15964909 0.38357660 -0.03908638
936 | v 0.31000046 0.17686067 0.02858093
937 | v -0.16838673 -0.50424527 -0.11351286
938 | v -0.24160017 0.43308230 -0.33009694
939 | v 0.16017903 0.26228855 -0.14158776
940 | v 0.25985519 0.06269953 0.07472941
941 | v -0.16531473 0.11008574 0.42466214
942 | v -0.19544593 -0.31156533 0.20867731
943 | v 0.06447344 -0.18815814 0.26180451
944 | v -0.23422992 0.22272050 0.44970675
945 | v -0.24471312 0.45262486 -0.27938390
946 | v -0.03809552 -0.50548371 0.17838301
947 | v -0.17340943 -0.49396959 0.10569943
948 | v 0.04244465 -0.23556094 -0.25027781
949 | v -0.12312078 0.42956737 -0.27570894
950 | v 0.05535474 -0.42592849 -0.05633489
951 | v -0.17255182 -0.49587375 0.13400388
952 | v -0.18628622 0.14767928 -0.41602688
953 | v 0.18008050 0.37563136 -0.01008920
954 | v 0.18965747 -0.18937103 0.00539218
955 | v -0.24995853 0.24141299 0.44439679
956 | v 0.13375220 0.40352405 -0.04409065
957 | v -0.08447500 -0.22478815 -0.30458551
958 | v -0.04939532 0.49350983 -0.09897918
959 | v 0.29020660 0.23122056 0.05802310
960 | v -0.08101131 -0.06290138 -0.35952129
961 | v -0.18912779 -0.23640090 0.26928378
962 | v -0.26222347 0.05187063 -0.18115638
963 | v -0.17636619 -0.13038077 -0.30811710
964 | v -0.25413642 -0.01548557 -0.16430528
965 | v 0.22314742 0.31454540 -0.05727778
966 | v 0.02332406 -0.28829832 -0.23983504
967 | v -0.20724490 -0.09402565 -0.30543386
968 | v -0.14279433 0.38171807 0.35838363
969 | v 0.13311479 0.38172808 -0.07404867
970 | v 0.10169847 -0.21031720 0.18675792
971 | v -0.19503881 0.41434250 -0.36241204
972 | v -0.17532937 -0.41913359 -0.17107880
973 | v -0.20607753 -0.35573306 0.00286034
974 | v 0.09810935 -0.27615033 0.14954424
975 | v -0.15680265 -0.50862967 -0.11856317
976 | v -0.24217608 -0.08271827 -0.16321318
977 | v 0.21594104 0.20733039 0.12688387
978 | v -0.02620935 0.49670516 0.09481066
979 | v -0.20701191 -0.35755670 0.06050973
980 | v -0.22194439 0.11895036 -0.39398349
981 | v -0.27351303 0.20403642 -0.31660671
982 | v -0.15865607 0.49246210 -0.07104189
983 | v -0.18419719 -0.44972644 0.11594350
984 | v -0.27178246 0.31718389 -0.37923177
985 | v 0.12165370 -0.31192269 0.06376813
986 | v -0.18291462 0.47467779 0.27820011
987 | v 0.05201915 -0.04154501 0.32036538
988 | v -0.08095237 0.45294083 0.22439393
989 | v 0.09015803 -0.02570419 -0.26680037
990 | v 0.00035324 0.31695929 0.27096480
991 | v -0.08868100 0.36438927 0.32253219
992 | v 0.04047612 -0.03971276 -0.31095047
993 | v 0.09694204 -0.10210074 0.26465529
994 | v -0.22206211 0.45693593 -0.30619305
995 | v -0.25016579 -0.07723123 0.03896744
996 | v -0.18328579 0.48350855 -0.22774179
997 | v -0.16623618 0.41870757 0.34590363
998 | v 0.10658814 -0.33485741 0.03984540
999 | v -0.28504833 0.25383667 0.13156662
1000 | v -0.24791809 0.42639335 0.34769729
1001 | v -0.25315073 0.04000841 0.25452732
1002 | v 0.18074367 -0.20844384 -0.03136446
1003 | v -0.22684673 -0.19435377 0.13545618
1004 | f 971 588 529
1005 | f 971 529 708
1006 | f 574 936 850
1007 | f 93 574 850
1008 | f 971 574 93
1009 | f 93 850 772
1010 | f 93 772 563
1011 | f 850 936 423
1012 | f 850 423 946
1013 | f 850 174 772
1014 | f 93 563 588
1015 | f 971 93 588
1016 | f 563 772 364
1017 | f 588 563 260
1018 | f 588 260 524
1019 | f 563 364 260
1020 | f 260 364 22
1021 | f 772 48 66
1022 | f 772 66 364
1023 | f 772 733 48
1024 | f 66 48 173
1025 | f 66 896 364
1026 | f 364 383 22
1027 | f 66 138 896
1028 | f 66 173 138
1029 | f 364 896 972
1030 | f 708 613 74
1031 | f 414 74 613
1032 | f 414 613 703
1033 | f 414 703 83
1034 | f 56 414 83
1035 | f 613 708 404
1036 | f 613 404 140
1037 | f 83 140 704
1038 | f 83 704 843
1039 | f 140 703 613
1040 | f 703 140 83
1041 | f 140 570 704
1042 | f 56 666 26
1043 | f 56 599 666
1044 | f 83 599 56
1045 | f 599 215 666
1046 | f 215 599 568
1047 | f 83 843 599
1048 | f 599 843 629
1049 | f 843 534 629
1050 | f 704 734 843
1051 | f 843 734 413
1052 | f 413 534 843
1053 | f 599 629 568
1054 | f 568 629 700
1055 | f 534 167 629
1056 | f 629 167 700
1057 | f 74 936 574
1058 | f 74 974 936
1059 | f 74 95 974
1060 | f 95 649 974
1061 | f 74 26 95
1062 | f 74 414 26
1063 | f 74 574 971
1064 | f 74 971 708
1065 | f 26 414 56
1066 | f 26 281 95
1067 | f 95 671 649
1068 | f 936 974 423
1069 | f 26 666 281
1070 | f 281 671 95
1071 | f 281 666 617
1072 | f 281 617 261
1073 | f 281 261 671
1074 | f 261 617 931
1075 | f 261 931 869
1076 | f 671 261 348
1077 | f 261 869 348
1078 | f 931 206 869
1079 | f 348 869 291
1080 | f 666 215 617
1081 | f 215 568 617
1082 | f 617 539 931
1083 | f 617 568 539
1084 | f 931 539 295
1085 | f 931 295 206
1086 | f 568 700 539
1087 | f 671 646 649
1088 | f 671 348 646
1089 | f 646 348 741
1090 | f 200 427 234
1091 | f 191 87 60
1092 | f 191 60 200
1093 | f 60 87 518
1094 | f 200 445 427
1095 | f 200 60 445
1096 | f 427 445 959
1097 | f 427 959 244
1098 | f 60 302 445
1099 | f 518 302 60
1100 | f 445 302 959
1101 | f 959 302 628
1102 | f 302 463 628
1103 | f 244 959 255
1104 | f 959 628 255
1105 | f 463 172 628
1106 | f 715 708 529
1107 | f 588 524 529
1108 | f 708 715 99
1109 | f 708 82 404
1110 | f 708 99 82
1111 | f 82 150 404
1112 | f 715 529 99
1113 | f 529 540 99
1114 | f 597 150 82
1115 | f 811 150 597
1116 | f 359 866 201
1117 | f 201 866 910
1118 | f 82 99 454
1119 | f 99 540 454
1120 | f 454 540 201
1121 | f 540 359 201
1122 | f 454 597 82
1123 | f 524 260 22
1124 | f 524 22 658
1125 | f 658 22 219
1126 | f 529 524 658
1127 | f 658 219 922
1128 | f 658 922 540
1129 | f 529 658 540
1130 | f 219 265 922
1131 | f 383 430 558
1132 | f 265 558 235
1133 | f 22 383 219
1134 | f 364 972 383
1135 | f 219 383 265
1136 | f 383 972 825
1137 | f 383 825 430
1138 | f 265 383 558
1139 | f 922 265 866
1140 | f 265 311 866
1141 | f 866 785 910
1142 | f 866 311 785
1143 | f 311 474 785
1144 | f 540 922 359
1145 | f 922 866 359
1146 | f 265 235 311
1147 | f 311 235 474
1148 | f 235 558 195
1149 | f 235 195 337
1150 | f 235 337 474
1151 | f 785 474 447
1152 | f 195 55 337
1153 | f 57 381 439
1154 | f 597 454 381
1155 | f 966 597 57
1156 | f 811 597 966
1157 | f 811 966 962
1158 | f 597 381 57
1159 | f 403 962 966
1160 | f 454 201 381
1161 | f 381 201 909
1162 | f 201 910 32
1163 | f 909 201 32
1164 | f 439 381 909
1165 | f 909 32 650
1166 | f 32 324 650
1167 | f 966 57 92
1168 | f 522 370 57
1169 | f 57 439 522
1170 | f 92 403 966
1171 | f 92 274 403
1172 | f 370 92 57
1173 | f 522 73 370
1174 | f 403 274 191
1175 | f 909 650 439
1176 | f 439 91 522
1177 | f 650 91 439
1178 | f 650 975 91
1179 | f 91 975 805
1180 | f 522 91 73
1181 | f 73 91 151
1182 | f 91 179 151
1183 | f 91 805 179
1184 | f 805 963 179
1185 | f 910 785 324
1186 | f 785 693 324
1187 | f 32 910 324
1188 | f 650 324 975
1189 | f 324 693 975
1190 | f 693 114 975
1191 | f 447 474 337
1192 | f 693 447 114
1193 | f 447 706 114
1194 | f 785 447 693
1195 | f 447 337 706
1196 | f 337 55 706
1197 | f 55 791 706
1198 | f 114 706 565
1199 | f 706 739 565
1200 | f 975 736 805
1201 | f 975 114 736
1202 | f 736 114 139
1203 | f 805 736 963
1204 | f 963 736 19
1205 | f 139 114 839
1206 | f 736 139 19
1207 | f 114 565 925
1208 | f 925 565 106
1209 | f 565 120 106
1210 | f 565 994 120
1211 | f 114 925 839
1212 | f 365 500 570
1213 | f 404 570 140
1214 | f 404 365 570
1215 | f 150 365 404
1216 | f 570 500 704
1217 | f 365 150 492
1218 | f 150 811 492
1219 | f 492 811 72
1220 | f 811 121 72
1221 | f 492 72 365
1222 | f 365 4 500
1223 | f 365 956 4
1224 | f 365 72 956
1225 | f 956 72 644
1226 | f 234 644 72
1227 | f 4 881 290
1228 | f 249 290 556
1229 | f 249 556 642
1230 | f 704 4 734
1231 | f 704 500 4
1232 | f 734 4 290
1233 | f 734 249 413
1234 | f 734 290 249
1235 | f 534 413 721
1236 | f 413 249 721
1237 | f 249 642 721
1238 | f 534 721 167
1239 | f 721 642 965
1240 | f 4 956 425
1241 | f 425 881 4
1242 | f 481 881 425
1243 | f 956 600 425
1244 | f 956 644 600
1245 | f 425 600 481
1246 | f 481 600 901
1247 | f 290 881 556
1248 | f 556 512 642
1249 | f 881 481 901
1250 | f 881 8 556
1251 | f 881 901 8
1252 | f 556 8 927
1253 | f 121 811 962
1254 | f 403 200 962
1255 | f 962 200 121
1256 | f 121 234 72
1257 | f 234 333 644
1258 | f 200 234 121
1259 | f 234 427 333
1260 | f 333 427 244
1261 | f 200 403 191
1262 | f 644 77 600
1263 | f 644 333 77
1264 | f 600 458 901
1265 | f 600 77 458
1266 | f 333 244 77
1267 | f 244 255 77
1268 | f 77 255 371
1269 | f 458 334 901
1270 | f 901 334 8
1271 | f 458 77 371
1272 | f 458 39 334
1273 | f 458 371 39
1274 | f 371 467 39
1275 | f 255 628 172
1276 | f 172 47 255
1277 | f 371 255 467
1278 | f 255 47 467
1279 | f 467 47 861
1280 | f 946 423 950
1281 | f 850 946 174
1282 | f 174 733 772
1283 | f 946 733 174
1284 | f 851 733 982
1285 | f 733 946 982
1286 | f 946 950 982
1287 | f 982 950 833
1288 | f 950 888 610
1289 | f 950 610 833
1290 | f 173 510 138
1291 | f 733 851 48
1292 | f 48 851 510
1293 | f 48 510 173
1294 | f 138 978 896
1295 | f 896 978 972
1296 | f 510 280 138
1297 | f 138 280 978
1298 | f 280 310 978
1299 | f 851 982 833
1300 | f 510 851 885
1301 | f 851 833 885
1302 | f 833 610 768
1303 | f 510 885 280
1304 | f 885 894 280
1305 | f 885 833 894
1306 | f 833 768 894
1307 | f 610 278 768
1308 | f 894 768 780
1309 | f 768 278 780
1310 | f 974 649 797
1311 | f 423 974 797
1312 | f 649 189 797
1313 | f 423 758 950
1314 | f 423 797 758
1315 | f 950 758 888
1316 | f 797 919 758
1317 | f 797 189 919
1318 | f 758 919 620
1319 | f 888 758 610
1320 | f 649 646 189
1321 | f 919 499 267
1322 | f 919 78 499
1323 | f 189 78 919
1324 | f 189 646 78
1325 | f 348 291 741
1326 | f 741 291 591
1327 | f 591 291 719
1328 | f 78 945 499
1329 | f 78 646 945
1330 | f 945 837 562
1331 | f 646 741 688
1332 | f 741 591 688
1333 | f 591 376 16
1334 | f 945 688 837
1335 | f 646 688 945
1336 | f 591 16 688
1337 | f 688 16 837
1338 | f 376 817 16
1339 | f 16 710 837
1340 | f 837 710 602
1341 | f 817 710 16
1342 | f 758 278 610
1343 | f 758 620 278
1344 | f 620 919 267
1345 | f 267 499 640
1346 | f 499 623 640
1347 | f 278 620 237
1348 | f 620 769 237
1349 | f 237 769 533
1350 | f 620 267 769
1351 | f 267 640 769
1352 | f 533 769 9
1353 | f 640 123 769
1354 | f 769 123 9
1355 | f 945 562 499
1356 | f 499 562 52
1357 | f 499 52 623
1358 | f 837 602 562
1359 | f 562 602 52
1360 | f 52 602 677
1361 | f 52 677 49
1362 | f 710 663 602
1363 | f 52 49 623
1364 | f 623 49 657
1365 | f 640 623 657
1366 | f 640 657 123
1367 | f 123 657 800
1368 | f 677 602 663
1369 | f 677 663 402
1370 | f 677 402 49
1371 | f 657 49 462
1372 | f 49 402 462
1373 | f 462 402 207
1374 | f 657 462 800
1375 | f 430 659 558
1376 | f 558 659 452
1377 | f 972 978 825
1378 | f 430 825 659
1379 | f 825 275 659
1380 | f 825 978 275
1381 | f 978 310 275
1382 | f 659 275 667
1383 | f 280 886 310
1384 | f 310 886 275
1385 | f 280 894 941
1386 | f 280 941 886
1387 | f 886 941 275
1388 | f 667 275 863
1389 | f 941 662 275
1390 | f 558 452 195
1391 | f 195 452 148
1392 | f 452 659 148
1393 | f 195 148 55
1394 | f 659 238 148
1395 | f 238 630 148
1396 | f 55 148 232
1397 | f 148 630 23
1398 | f 148 367 232
1399 | f 148 23 367
1400 | f 659 667 238
1401 | f 667 863 238
1402 | f 630 1002 23
1403 | f 630 238 1002
1404 | f 863 357 238
1405 | f 238 587 1002
1406 | f 275 357 863
1407 | f 894 780 941
1408 | f 278 186 780
1409 | f 278 237 186
1410 | f 941 287 662
1411 | f 941 780 287
1412 | f 780 960 287
1413 | f 780 186 960
1414 | f 287 960 343
1415 | f 960 186 921
1416 | f 357 660 238
1417 | f 275 607 357
1418 | f 275 662 607
1419 | f 662 287 607
1420 | f 287 343 607
1421 | f 607 343 284
1422 | f 343 960 284
1423 | f 357 607 660
1424 | f 607 241 660
1425 | f 284 745 607
1426 | f 607 745 241
1427 | f 284 505 745
1428 | f 505 960 307
1429 | f 960 505 284
1430 | f 960 921 307
1431 | f 55 41 791
1432 | f 55 367 41
1433 | f 55 232 367
1434 | f 41 367 791
1435 | f 706 791 739
1436 | f 791 367 152
1437 | f 791 152 739
1438 | f 367 752 152
1439 | f 367 38 752
1440 | f 367 23 38
1441 | f 38 23 426
1442 | f 1002 587 23
1443 | f 587 654 23
1444 | f 752 38 426
1445 | f 752 426 643
1446 | f 426 390 643
1447 | f 426 23 390
1448 | f 565 739 994
1449 | f 994 739 152
1450 | f 152 784 994
1451 | f 152 594 784
1452 | f 152 752 594
1453 | f 994 317 120
1454 | f 994 784 317
1455 | f 752 764 594
1456 | f 594 764 784
1457 | f 752 643 764
1458 | f 764 643 720
1459 | f 784 764 317
1460 | f 764 720 317
1461 | f 587 238 654
1462 | f 654 238 684
1463 | f 238 660 241
1464 | f 238 241 684
1465 | f 23 589 390
1466 | f 23 654 589
1467 | f 390 589 436
1468 | f 684 241 589
1469 | f 654 684 589
1470 | f 241 718 589
1471 | f 589 718 216
1472 | f 241 745 718
1473 | f 505 307 14
1474 | f 745 505 718
1475 | f 505 913 718
1476 | f 505 14 913
1477 | f 14 689 913
1478 | f 390 436 643
1479 | f 589 327 436
1480 | f 589 216 327
1481 | f 643 243 720
1482 | f 720 243 1000
1483 | f 436 327 643
1484 | f 243 643 498
1485 | f 718 601 216
1486 | f 216 601 229
1487 | f 327 28 643
1488 | f 327 216 28
1489 | f 216 229 28
1490 | f 28 229 770
1491 | f 229 601 770
1492 | f 601 605 770
1493 | f 601 795 605
1494 | f 718 913 601
1495 | f 913 689 661
1496 | f 913 795 601
1497 | f 237 533 186
1498 | f 533 655 186
1499 | f 9 123 61
1500 | f 9 61 533
1501 | f 533 61 655
1502 | f 655 61 391
1503 | f 186 307 921
1504 | f 186 655 391
1505 | f 186 391 307
1506 | f 391 61 488
1507 | f 307 391 488
1508 | f 307 488 459
1509 | f 307 459 773
1510 | f 488 194 459
1511 | f 800 462 43
1512 | f 462 207 43
1513 | f 800 43 923
1514 | f 923 43 503
1515 | f 123 800 61
1516 | f 800 923 61
1517 | f 923 635 61
1518 | f 61 194 488
1519 | f 61 635 194
1520 | f 194 635 416
1521 | f 194 416 683
1522 | f 923 107 635
1523 | f 923 503 107
1524 | f 635 107 346
1525 | f 503 803 107
1526 | f 635 346 416
1527 | f 307 689 14
1528 | f 307 773 689
1529 | f 773 668 689
1530 | f 661 166 913
1531 | f 459 194 429
1532 | f 194 683 429
1533 | f 459 429 773
1534 | f 429 683 236
1535 | f 773 429 668
1536 | f 668 429 472
1537 | f 429 236 472
1538 | f 236 713 472
1539 | f 416 476 683
1540 | f 683 713 236
1541 | f 416 346 476
1542 | f 683 476 713
1543 | f 713 51 472
1544 | f 713 569 51
1545 | f 713 476 569
1546 | f 668 472 898
1547 | f 898 472 827
1548 | f 827 472 325
1549 | f 472 51 781
1550 | f 51 569 781
1551 | f 781 325 472
1552 | f 913 166 795
1553 | f 795 166 605
1554 | f 668 203 689
1555 | f 661 409 166
1556 | f 689 203 661
1557 | f 661 203 551
1558 | f 203 898 551
1559 | f 661 551 409
1560 | f 409 551 673
1561 | f 409 673 166
1562 | f 166 673 547
1563 | f 551 827 673
1564 | f 203 668 898
1565 | f 551 898 827
1566 | f 92 396 274
1567 | f 92 932 396
1568 | f 396 979 274
1569 | f 274 87 191
1570 | f 274 494 87
1571 | f 494 274 979
1572 | f 396 932 40
1573 | f 497 40 932
1574 | f 384 396 40
1575 | f 384 979 396
1576 | f 979 102 245
1577 | f 40 497 571
1578 | f 979 245 494
1579 | f 494 636 782
1580 | f 494 245 636
1581 | f 245 951 636
1582 | f 245 102 951
1583 | f 951 406 636
1584 | f 464 571 497
1585 | f 571 464 486
1586 | f 486 464 76
1587 | f 464 980 76
1588 | f 76 90 486
1589 | f 90 709 103
1590 | f 90 983 709
1591 | f 90 76 697
1592 | f 90 697 983
1593 | f 983 305 709
1594 | f 878 29 590
1595 | f 878 30 104
1596 | f 590 30 878
1597 | f 384 29 102
1598 | f 40 571 384
1599 | f 102 979 384
1600 | f 29 878 102
1601 | f 102 878 124
1602 | f 384 486 116
1603 | f 384 116 29
1604 | f 384 571 486
1605 | f 116 590 29
1606 | f 878 104 124
1607 | f 102 124 951
1608 | f 951 124 406
1609 | f 124 928 406
1610 | f 406 928 112
1611 | f 124 104 928
1612 | f 104 580 250
1613 | f 928 250 112
1614 | f 928 104 250
1615 | f 116 486 90
1616 | f 116 30 590
1617 | f 116 103 212
1618 | f 116 90 103
1619 | f 116 466 30
1620 | f 116 212 466
1621 | f 30 580 104
1622 | f 30 466 592
1623 | f 30 592 580
1624 | f 103 254 212
1625 | f 212 254 466
1626 | f 103 709 891
1627 | f 103 891 254
1628 | f 466 130 592
1629 | f 466 868 130
1630 | f 466 254 868
1631 | f 130 868 592
1632 | f 868 269 592
1633 | f 580 350 250
1634 | f 580 567 350
1635 | f 580 592 567
1636 | f 567 592 269
1637 | f 87 494 782
1638 | f 87 724 680
1639 | f 87 782 724
1640 | f 87 680 518
1641 | f 680 724 518
1642 | f 782 788 724
1643 | f 518 724 262
1644 | f 518 262 463
1645 | f 302 518 463
1646 | f 463 262 676
1647 | f 724 299 262
1648 | f 788 299 724
1649 | f 782 636 788
1650 | f 636 397 788
1651 | f 788 397 299
1652 | f 636 406 397
1653 | f 397 204 299
1654 | f 204 112 358
1655 | f 397 112 204
1656 | f 262 299 493
1657 | f 299 50 493
1658 | f 299 204 50
1659 | f 204 358 50
1660 | f 463 676 172
1661 | f 262 493 676
1662 | f 676 493 892
1663 | f 493 50 892
1664 | f 406 112 397
1665 | f 112 742 358
1666 | f 112 350 564
1667 | f 112 564 742
1668 | f 358 742 84
1669 | f 112 250 350
1670 | f 567 269 68
1671 | f 567 401 350
1672 | f 567 68 401
1673 | f 350 401 564
1674 | f 257 731 301
1675 | f 497 301 732
1676 | f 301 497 257
1677 | f 497 732 464
1678 | f 257 638 731
1679 | f 638 147 731
1680 | f 638 961 147
1681 | f 961 877 147
1682 | f 961 443 877
1683 | f 301 731 198
1684 | f 198 731 147
1685 | f 877 342 309
1686 | f 669 370 73
1687 | f 73 181 669
1688 | f 92 370 669
1689 | f 92 669 932
1690 | f 73 151 181
1691 | f 669 181 686
1692 | f 932 669 686
1693 | f 686 181 257
1694 | f 179 961 151
1695 | f 179 963 961
1696 | f 181 151 638
1697 | f 181 638 257
1698 | f 151 961 638
1699 | f 932 686 497
1700 | f 686 257 497
1701 | f 443 67 342
1702 | f 19 67 443
1703 | f 19 839 67
1704 | f 382 839 106
1705 | f 839 382 67
1706 | f 67 382 362
1707 | f 382 106 872
1708 | f 106 46 872
1709 | f 106 647 46
1710 | f 877 443 342
1711 | f 342 67 912
1712 | f 67 362 912
1713 | f 342 912 309
1714 | f 382 315 362
1715 | f 382 872 315
1716 | f 872 46 315
1717 | f 362 315 372
1718 | f 315 471 372
1719 | f 315 748 471
1720 | f 315 46 748
1721 | f 963 19 443
1722 | f 963 443 961
1723 | f 139 839 19
1724 | f 839 925 106
1725 | f 120 647 106
1726 | f 464 732 980
1727 | f 732 301 980
1728 | f 301 198 980
1729 | f 980 198 351
1730 | f 76 980 762
1731 | f 980 911 762
1732 | f 980 351 911
1733 | f 198 2 351
1734 | f 198 147 2
1735 | f 2 147 621
1736 | f 877 621 147
1737 | f 877 309 621
1738 | f 621 437 2
1739 | f 351 2 911
1740 | f 621 309 395
1741 | f 621 395 437
1742 | f 76 762 697
1743 | f 697 762 763
1744 | f 697 444 983
1745 | f 983 5 305
1746 | f 983 444 5
1747 | f 444 697 763
1748 | f 2 437 911
1749 | f 911 437 762
1750 | f 762 437 763
1751 | f 437 395 164
1752 | f 437 799 763
1753 | f 437 164 799
1754 | f 309 912 374
1755 | f 912 362 372
1756 | f 374 912 468
1757 | f 912 372 468
1758 | f 309 374 395
1759 | f 374 468 395
1760 | f 471 748 728
1761 | f 471 606 468
1762 | f 471 7 606
1763 | f 372 471 468
1764 | f 7 471 728
1765 | f 728 502 7
1766 | f 395 765 164
1767 | f 468 110 395
1768 | f 395 110 765
1769 | f 110 926 765
1770 | f 164 765 799
1771 | f 765 926 144
1772 | f 765 144 799
1773 | f 468 606 110
1774 | f 110 606 845
1775 | f 606 634 845
1776 | f 110 845 926
1777 | f 926 845 144
1778 | f 676 142 172
1779 | f 676 892 142
1780 | f 172 142 47
1781 | f 47 142 648
1782 | f 358 84 50
1783 | f 892 50 890
1784 | f 142 890 648
1785 | f 142 892 890
1786 | f 890 308 648
1787 | f 742 398 84
1788 | f 742 564 398
1789 | f 564 401 398
1790 | f 398 401 491
1791 | f 50 84 890
1792 | f 890 84 735
1793 | f 890 735 308
1794 | f 308 735 220
1795 | f 398 735 84
1796 | f 398 491 735
1797 | f 401 428 491
1798 | f 428 117 491
1799 | f 117 902 491
1800 | f 735 491 283
1801 | f 491 902 283
1802 | f 220 735 283
1803 | f 283 902 672
1804 | f 902 35 672
1805 | f 709 305 408
1806 | f 408 305 253
1807 | f 254 891 363
1808 | f 254 852 868
1809 | f 868 852 269
1810 | f 254 363 852
1811 | f 891 709 408
1812 | f 363 432 852
1813 | f 891 70 363
1814 | f 891 937 70
1815 | f 891 408 937
1816 | f 363 70 432
1817 | f 70 306 432
1818 | f 432 970 852
1819 | f 269 852 682
1820 | f 852 970 682
1821 | f 269 682 68
1822 | f 432 306 970
1823 | f 682 970 465
1824 | f 305 5 253
1825 | f 444 763 5
1826 | f 253 5 598
1827 | f 253 598 604
1828 | f 5 353 598
1829 | f 763 799 5
1830 | f 408 253 937
1831 | f 253 944 937
1832 | f 253 604 944
1833 | f 937 993 306
1834 | f 70 937 306
1835 | f 306 993 664
1836 | f 604 598 944
1837 | f 970 306 664
1838 | f 970 664 465
1839 | f 465 664 480
1840 | f 598 757 944
1841 | f 993 131 664
1842 | f 937 944 993
1843 | f 944 199 900
1844 | f 993 944 900
1845 | f 993 900 131
1846 | f 664 131 480
1847 | f 131 900 519
1848 | f 131 259 480
1849 | f 944 757 199
1850 | f 199 808 900
1851 | f 199 132 808
1852 | f 132 544 808
1853 | f 900 995 519
1854 | f 900 808 753
1855 | f 900 753 995
1856 | f 808 544 753
1857 | f 753 330 995
1858 | f 753 544 330
1859 | f 544 532 330
1860 | f 5 695 353
1861 | f 799 144 5
1862 | f 695 615 353
1863 | f 353 615 598
1864 | f 144 695 5
1865 | f 598 596 757
1866 | f 757 314 199
1867 | f 132 907 544
1868 | f 532 344 330
1869 | f 544 907 532
1870 | f 532 981 344
1871 | f 314 579 199
1872 | f 199 579 132
1873 | f 132 579 907
1874 | f 532 538 64
1875 | f 532 64 981
1876 | f 532 907 538
1877 | f 68 682 428
1878 | f 428 682 816
1879 | f 68 428 401
1880 | f 682 465 816
1881 | f 465 480 948
1882 | f 480 760 948
1883 | f 465 948 816
1884 | f 428 816 117
1885 | f 816 948 178
1886 | f 816 178 117
1887 | f 948 760 871
1888 | f 178 948 871
1889 | f 480 259 760
1890 | f 131 519 259
1891 | f 519 804 259
1892 | f 519 995 804
1893 | f 995 122 804
1894 | f 995 330 122
1895 | f 760 259 871
1896 | f 804 122 259
1897 | f 871 259 379
1898 | f 259 122 379
1899 | f 117 178 168
1900 | f 117 168 902
1901 | f 902 168 36
1902 | f 168 178 36
1903 | f 36 178 230
1904 | f 178 871 230
1905 | f 871 379 230
1906 | f 36 35 902
1907 | f 36 614 35
1908 | f 36 230 614
1909 | f 330 344 665
1910 | f 122 330 21
1911 | f 330 665 21
1912 | f 21 484 122
1913 | f 122 844 379
1914 | f 122 484 844
1915 | f 21 224 484
1916 | f 21 665 224
1917 | f 981 586 344
1918 | f 981 690 586
1919 | f 981 64 690
1920 | f 344 12 665
1921 | f 344 586 12
1922 | f 665 12 224
1923 | f 224 12 45
1924 | f 586 690 12
1925 | f 614 230 366
1926 | f 230 379 631
1927 | f 631 379 698
1928 | f 379 844 698
1929 | f 844 957 698
1930 | f 844 484 957
1931 | f 484 224 957
1932 | f 230 631 366
1933 | f 698 438 631
1934 | f 224 190 957
1935 | f 224 45 190
1936 | f 957 190 175
1937 | f 45 515 190
1938 | f 190 515 62
1939 | f 698 175 438
1940 | f 175 20 438
1941 | f 175 214 20
1942 | f 957 175 698
1943 | f 190 214 175
1944 | f 190 62 214
1945 | f 647 521 221
1946 | f 521 705 221
1947 | f 647 221 46
1948 | f 221 705 626
1949 | f 221 626 722
1950 | f 626 705 479
1951 | f 479 169 626
1952 | f 169 479 723
1953 | f 46 221 730
1954 | f 221 722 730
1955 | f 46 801 748
1956 | f 46 730 801
1957 | f 748 801 728
1958 | f 730 722 801
1959 | f 722 626 134
1960 | f 626 169 134
1961 | f 511 722 893
1962 | f 801 722 511
1963 | f 893 722 134
1964 | f 893 134 369
1965 | f 120 521 647
1966 | f 120 317 521
1967 | f 317 705 521
1968 | f 317 479 705
1969 | f 317 720 360
1970 | f 317 360 723
1971 | f 317 723 479
1972 | f 169 723 360
1973 | f 360 10 169
1974 | f 169 369 134
1975 | f 169 10 369
1976 | f 611 846 10
1977 | f 10 239 369
1978 | f 369 778 893
1979 | f 369 239 778
1980 | f 10 557 239
1981 | f 10 846 557
1982 | f 239 557 559
1983 | f 557 581 559
1984 | f 581 557 823
1985 | f 557 354 823
1986 | f 720 1000 360
1987 | f 243 498 1000
1988 | f 643 193 498
1989 | f 360 1000 10
1990 | f 498 450 1000
1991 | f 498 193 450
1992 | f 1000 611 10
1993 | f 1000 846 611
1994 | f 1000 450 846
1995 | f 643 28 193
1996 | f 28 809 193
1997 | f 28 779 809
1998 | f 28 770 779
1999 | f 193 809 450
2000 | f 779 6 809
2001 | f 846 450 557
2002 | f 809 6 450
2003 | f 557 450 354
2004 | f 450 818 354
2005 | f 801 271 728
2006 | f 728 271 502
2007 | f 801 285 271
2008 | f 7 502 162
2009 | f 271 44 502
2010 | f 502 44 162
2011 | f 271 285 44
2012 | f 801 511 285
2013 | f 511 893 176
2014 | f 511 176 285
2015 | f 285 998 44
2016 | f 998 285 495
2017 | f 285 176 495
2018 | f 7 162 606
2019 | f 606 162 634
2020 | f 845 634 144
2021 | f 634 695 144
2022 | f 44 998 162
2023 | f 162 998 65
2024 | f 162 525 634
2025 | f 162 65 525
2026 | f 998 495 65
2027 | f 634 525 695
2028 | f 893 778 176
2029 | f 239 559 778
2030 | f 778 559 451
2031 | f 778 451 223
2032 | f 176 778 223
2033 | f 176 223 386
2034 | f 559 581 451
2035 | f 581 645 451
2036 | f 581 823 645
2037 | f 451 386 223
2038 | f 451 645 386
2039 | f 645 774 386
2040 | f 495 176 119
2041 | f 495 119 65
2042 | f 176 386 119
2043 | f 119 482 65
2044 | f 65 482 525
2045 | f 119 386 482
2046 | f 386 774 240
2047 | f 386 240 482
2048 | f 482 419 525
2049 | f 482 240 513
2050 | f 325 393 829
2051 | f 325 781 393
2052 | f 393 347 829
2053 | f 829 347 777
2054 | f 777 433 226
2055 | f 226 433 105
2056 | f 829 777 226
2057 | f 555 433 777
2058 | f 196 860 326
2059 | f 990 326 860
2060 | f 433 75 105
2061 | f 433 916 75
2062 | f 196 535 916
2063 | f 535 75 916
2064 | f 536 916 433
2065 | f 555 3 433
2066 | f 433 3 536
2067 | f 489 989 930
2068 | f 930 141 489
2069 | f 196 916 860
2070 | f 989 860 916
2071 | f 489 990 860
2072 | f 860 989 489
2073 | f 536 793 916
2074 | f 989 916 793
2075 | f 770 605 125
2076 | f 779 770 125
2077 | f 779 268 6
2078 | f 779 125 268
2079 | f 605 282 125
2080 | f 605 166 282
2081 | f 125 145 268
2082 | f 125 375 145
2083 | f 282 375 125
2084 | f 450 6 818
2085 | f 6 268 818
2086 | f 354 818 823
2087 | f 818 146 823
2088 | f 818 268 146
2089 | f 268 153 146
2090 | f 268 883 153
2091 | f 268 145 883
2092 | f 375 940 145
2093 | f 145 940 453
2094 | f 145 743 883
2095 | f 145 453 743
2096 | f 453 940 335
2097 | f 823 331 645
2098 | f 331 823 400
2099 | f 645 331 774
2100 | f 331 400 685
2101 | f 331 685 774
2102 | f 774 685 136
2103 | f 774 136 240
2104 | f 685 135 136
2105 | f 685 771 135
2106 | f 240 136 513
2107 | f 136 135 513
2108 | f 513 135 790
2109 | f 823 146 400
2110 | f 146 153 400
2111 | f 400 153 954
2112 | f 153 883 545
2113 | f 883 279 545
2114 | f 545 231 943
2115 | f 545 943 153
2116 | f 545 279 231
2117 | f 153 943 954
2118 | f 943 101 582
2119 | f 954 943 582
2120 | f 400 954 685
2121 | f 954 582 380
2122 | f 685 954 380
2123 | f 943 231 101
2124 | f 101 231 417
2125 | f 883 743 279
2126 | f 743 453 449
2127 | f 743 449 279
2128 | f 231 279 449
2129 | f 453 25 449
2130 | f 453 335 25
2131 | f 231 449 417
2132 | f 149 417 449
2133 | f 25 434 149
2134 | f 449 25 149
2135 | f 149 434 908
2136 | f 582 417 653
2137 | f 582 101 417
2138 | f 685 380 771
2139 | f 771 380 578
2140 | f 380 653 578
2141 | f 653 884 578
2142 | f 578 884 448
2143 | f 135 771 469
2144 | f 771 578 469
2145 | f 135 469 687
2146 | f 622 448 884
2147 | f 578 412 469
2148 | f 578 448 412
2149 | f 448 622 412
2150 | f 417 149 884
2151 | f 211 884 149
2152 | f 622 884 211
2153 | f 149 908 211
2154 | f 582 653 380
2155 | f 884 653 417
2156 | f 166 897 282
2157 | f 166 547 897
2158 | f 547 918 897
2159 | f 282 205 375
2160 | f 282 897 205
2161 | f 205 897 627
2162 | f 897 264 627
2163 | f 897 918 822
2164 | f 897 822 264
2165 | f 375 205 627
2166 | f 547 673 918
2167 | f 673 696 918
2168 | f 696 822 918
2169 | f 822 696 856
2170 | f 264 822 856
2171 | f 264 225 335
2172 | f 375 627 940
2173 | f 940 627 335
2174 | f 627 264 335
2175 | f 335 434 25
2176 | f 264 856 225
2177 | f 335 225 434
2178 | f 105 434 225
2179 | f 226 105 225
2180 | f 673 827 696
2181 | f 827 325 696
2182 | f 696 829 856
2183 | f 696 325 829
2184 | f 829 225 856
2185 | f 829 226 225
2186 | f 75 535 908
2187 | f 434 75 908
2188 | f 434 105 75
2189 | f 535 211 908
2190 | f 326 211 196
2191 | f 211 535 196
2192 | f 326 967 819
2193 | f 326 622 211
2194 | f 326 819 622
2195 | f 598 615 870
2196 | f 695 525 419
2197 | f 615 69 870
2198 | f 695 69 615
2199 | f 598 870 596
2200 | f 757 596 314
2201 | f 314 874 579
2202 | f 579 639 907
2203 | f 907 639 835
2204 | f 874 639 579
2205 | f 907 410 538
2206 | f 835 824 907
2207 | f 907 159 410
2208 | f 907 824 159
2209 | f 695 419 69
2210 | f 482 133 419
2211 | f 482 513 133
2212 | f 513 483 133
2213 | f 419 133 69
2214 | f 69 133 870
2215 | f 133 483 870
2216 | f 596 520 652
2217 | f 596 999 520
2218 | f 870 999 596
2219 | f 483 857 999
2220 | f 870 483 999
2221 | f 999 711 129
2222 | f 999 129 520
2223 | f 129 352 115
2224 | f 352 737 115
2225 | f 115 737 171
2226 | f 171 737 996
2227 | f 314 596 874
2228 | f 639 747 835
2229 | f 639 632 747
2230 | f 639 874 632
2231 | f 835 903 824
2232 | f 824 903 37
2233 | f 824 37 159
2234 | f 835 747 903
2235 | f 903 548 37
2236 | f 903 985 548
2237 | f 903 747 985
2238 | f 319 548 377
2239 | f 548 985 377
2240 | f 874 652 632
2241 | f 596 652 874
2242 | f 652 520 924
2243 | f 652 924 632
2244 | f 632 924 633
2245 | f 632 633 747
2246 | f 924 520 129
2247 | f 924 129 115
2248 | f 924 115 633
2249 | f 633 115 985
2250 | f 747 633 985
2251 | f 377 985 13
2252 | f 985 115 13
2253 | f 171 13 115
2254 | f 64 694 690
2255 | f 64 538 692
2256 | f 64 297 694
2257 | f 64 692 297
2258 | f 694 356 690
2259 | f 297 692 853
2260 | f 694 297 356
2261 | f 410 159 875
2262 | f 875 159 750
2263 | f 538 410 692
2264 | f 692 410 853
2265 | f 410 875 853
2266 | f 853 875 361
2267 | f 875 750 361
2268 | f 853 361 297
2269 | f 297 361 98
2270 | f 361 523 98
2271 | f 523 750 593
2272 | f 523 361 750
2273 | f 12 356 45
2274 | f 12 690 356
2275 | f 45 356 515
2276 | f 356 609 515
2277 | f 356 297 98
2278 | f 356 98 609
2279 | f 62 609 977
2280 | f 98 977 609
2281 | f 214 841 456
2282 | f 214 62 841
2283 | f 62 100 841
2284 | f 679 15 100
2285 | f 515 609 62
2286 | f 62 977 100
2287 | f 523 128 977
2288 | f 523 593 128
2289 | f 422 128 593
2290 | f 128 422 27
2291 | f 98 523 977
2292 | f 977 128 100
2293 | f 100 128 679
2294 | f 27 679 128
2295 | f 794 616 13
2296 | f 294 13 171
2297 | f 794 13 294
2298 | f 171 996 294
2299 | f 294 996 967
2300 | f 990 485 294
2301 | f 489 485 990
2302 | f 294 967 990
2303 | f 967 326 990
2304 | f 294 485 794
2305 | f 159 37 750
2306 | f 37 548 319
2307 | f 37 319 750
2308 | f 13 616 377
2309 | f 750 319 593
2310 | f 377 593 319
2311 | f 377 616 593
2312 | f 422 616 987
2313 | f 422 593 616
2314 | f 794 987 616
2315 | f 141 485 489
2316 | f 794 422 987
2317 | f 794 485 141
2318 | f 930 213 141
2319 | f 794 141 422
2320 | f 141 27 422
2321 | f 513 790 483
2322 | f 790 135 687
2323 | f 790 857 483
2324 | f 790 687 411
2325 | f 790 411 857
2326 | f 687 349 411
2327 | f 687 469 298
2328 | f 687 298 349
2329 | f 469 412 298
2330 | f 622 113 412
2331 | f 298 412 349
2332 | f 575 412 113
2333 | f 575 349 412
2334 | f 857 411 711
2335 | f 999 857 711
2336 | f 352 575 737
2337 | f 352 711 575
2338 | f 711 349 575
2339 | f 711 411 349
2340 | f 129 711 352
2341 | f 819 737 113
2342 | f 819 967 737
2343 | f 622 819 113
2344 | f 737 575 113
2345 | f 737 967 996
2346 | f 700 177 539
2347 | f 167 177 700
2348 | f 206 295 187
2349 | f 206 187 328
2350 | f 187 295 906
2351 | f 906 328 187
2352 | f 869 206 291
2353 | f 206 328 291
2354 | f 295 539 54
2355 | f 539 288 341
2356 | f 539 341 54
2357 | f 295 54 906
2358 | f 539 177 288
2359 | f 341 651 54
2360 | f 54 651 906
2361 | f 288 716 341
2362 | f 341 446 651
2363 | f 328 906 949
2364 | f 328 949 252
2365 | f 949 541 252
2366 | f 906 651 949
2367 | f 949 651 233
2368 | f 721 965 177
2369 | f 177 965 656
2370 | f 167 721 177
2371 | f 177 656 288
2372 | f 556 927 512
2373 | f 512 927 947
2374 | f 927 8 786
2375 | f 927 197 947
2376 | f 927 786 197
2377 | f 642 512 965
2378 | f 965 947 656
2379 | f 512 947 965
2380 | f 656 947 258
2381 | f 656 258 288
2382 | f 947 197 258
2383 | f 288 258 716
2384 | f 341 855 446
2385 | f 341 716 855
2386 | f 651 446 233
2387 | f 855 531 446
2388 | f 446 531 233
2389 | f 233 531 514
2390 | f 531 674 514
2391 | f 855 509 531
2392 | f 531 509 674
2393 | f 514 674 143
2394 | f 258 554 716
2395 | f 716 637 855
2396 | f 716 554 637
2397 | f 554 798 637
2398 | f 855 637 509
2399 | f 637 1001 509
2400 | f 674 953 143
2401 | f 637 798 618
2402 | f 637 618 1001
2403 | f 618 94 1001
2404 | f 509 1001 674
2405 | f 674 1001 953
2406 | f 8 345 786
2407 | f 8 334 345
2408 | f 786 862 197
2409 | f 786 345 862
2410 | f 334 39 345
2411 | f 345 39 862
2412 | f 39 182 862
2413 | f 862 300 197
2414 | f 862 192 300
2415 | f 258 197 256
2416 | f 197 300 108
2417 | f 39 467 991
2418 | f 39 991 182
2419 | f 467 861 991
2420 | f 861 163 991
2421 | f 991 163 59
2422 | f 862 182 192
2423 | f 192 182 988
2424 | f 192 81 300
2425 | f 182 991 988
2426 | f 991 59 988
2427 | f 192 988 81
2428 | f 988 59 435
2429 | f 197 108 256
2430 | f 256 905 258
2431 | f 108 300 915
2432 | f 300 81 915
2433 | f 988 821 81
2434 | f 81 566 915
2435 | f 256 108 415
2436 | f 108 915 415
2437 | f 258 905 554
2438 | f 905 727 554
2439 | f 554 727 798
2440 | f 798 441 618
2441 | f 256 415 905
2442 | f 415 915 270
2443 | f 618 441 94
2444 | f 1001 94 953
2445 | f 94 157 953
2446 | f 441 624 94
2447 | f 94 624 157
2448 | f 798 242 441
2449 | f 441 292 624
2450 | f 624 292 228
2451 | f 727 24 798
2452 | f 905 415 727
2453 | f 727 415 24
2454 | f 270 263 415
2455 | f 798 24 242
2456 | f 415 864 24
2457 | f 415 263 864
2458 | f 242 517 292
2459 | f 441 242 292
2460 | f 292 517 228
2461 | f 242 864 517
2462 | f 517 227 792
2463 | f 242 24 864
2464 | f 517 864 227
2465 | f 864 222 227
2466 | f 864 619 222
2467 | f 291 328 719
2468 | f 719 252 376
2469 | f 328 252 719
2470 | f 252 767 376
2471 | f 719 376 591
2472 | f 767 53 376
2473 | f 376 286 817
2474 | f 376 53 286
2475 | f 817 286 710
2476 | f 252 541 542
2477 | f 252 542 767
2478 | f 541 247 542
2479 | f 949 233 541
2480 | f 233 997 541
2481 | f 541 865 247
2482 | f 541 997 865
2483 | f 247 865 691
2484 | f 767 542 53
2485 | f 53 183 286
2486 | f 183 53 127
2487 | f 286 183 710
2488 | f 710 183 663
2489 | f 183 248 663
2490 | f 127 248 183
2491 | f 127 246 248
2492 | f 127 814 246
2493 | f 542 247 53
2494 | f 247 368 53
2495 | f 247 691 368
2496 | f 53 368 127
2497 | f 368 904 127
2498 | f 248 402 663
2499 | f 248 246 402
2500 | f 865 895 691
2501 | f 691 895 368
2502 | f 895 904 368
2503 | f 904 814 127
2504 | f 814 904 85
2505 | f 895 973 904
2506 | f 85 783 814
2507 | f 904 973 85
2508 | f 973 355 85
2509 | f 233 514 997
2510 | f 997 137 865
2511 | f 997 984 137
2512 | f 865 137 895
2513 | f 514 143 997
2514 | f 143 158 997
2515 | f 997 158 984
2516 | f 984 478 137
2517 | f 478 749 137
2518 | f 984 158 478
2519 | f 137 749 895
2520 | f 749 973 895
2521 | f 749 296 973
2522 | f 143 830 158
2523 | f 158 766 478
2524 | f 766 625 478
2525 | f 158 830 766
2526 | f 830 882 766
2527 | f 882 470 766
2528 | f 766 820 625
2529 | f 143 953 882
2530 | f 143 882 830
2531 | f 953 528 882
2532 | f 882 775 470
2533 | f 766 470 820
2534 | f 470 312 820
2535 | f 470 775 312
2536 | f 478 296 749
2537 | f 478 625 296
2538 | f 625 33 296
2539 | f 625 820 33
2540 | f 296 355 973
2541 | f 296 33 355
2542 | f 33 969 355
2543 | f 33 42 969
2544 | f 820 42 33
2545 | f 820 546 42
2546 | f 246 207 402
2547 | f 246 11 207
2548 | f 814 11 246
2549 | f 814 783 11
2550 | f 207 11 776
2551 | f 783 776 11
2552 | f 783 487 776
2553 | f 783 501 487
2554 | f 776 43 207
2555 | f 43 776 803
2556 | f 85 501 783
2557 | f 503 43 803
2558 | f 803 776 929
2559 | f 487 929 776
2560 | f 487 160 929
2561 | f 501 160 487
2562 | f 160 942 929
2563 | f 355 501 85
2564 | f 501 576 160
2565 | f 355 576 501
2566 | f 355 702 576
2567 | f 969 702 355
2568 | f 160 576 942
2569 | f 702 942 576
2570 | f 702 787 942
2571 | f 107 803 71
2572 | f 803 929 71
2573 | f 107 71 346
2574 | f 929 583 71
2575 | f 942 583 929
2576 | f 702 969 303
2577 | f 42 303 969
2578 | f 842 313 775
2579 | f 42 810 303
2580 | f 546 810 42
2581 | f 312 546 820
2582 | f 312 807 546
2583 | f 775 807 312
2584 | f 775 313 807
2585 | f 807 810 546
2586 | f 313 111 807
2587 | f 313 585 111
2588 | f 807 725 218
2589 | f 807 218 810
2590 | f 807 111 725
2591 | f 585 1 111
2592 | f 218 725 859
2593 | f 859 725 832
2594 | f 725 111 832
2595 | f 1 832 111
2596 | f 1 899 832
2597 | f 303 787 702
2598 | f 942 787 583
2599 | f 787 303 712
2600 | f 303 461 712
2601 | f 787 712 392
2602 | f 712 992 392
2603 | f 461 992 712
2604 | f 346 71 126
2605 | f 126 71 761
2606 | f 71 583 761
2607 | f 346 126 476
2608 | f 126 761 407
2609 | f 476 126 569
2610 | f 569 126 407
2611 | f 781 569 407
2612 | f 787 761 583
2613 | f 787 392 761
2614 | f 392 316 761
2615 | f 761 316 407
2616 | f 992 316 392
2617 | f 992 738 316
2618 | f 455 738 992
2619 | f 781 407 986
2620 | f 781 986 393
2621 | f 986 347 393
2622 | f 738 407 316
2623 | f 738 701 407
2624 | f 407 701 986
2625 | f 738 336 701
2626 | f 336 418 701
2627 | f 701 418 986
2628 | f 347 986 418
2629 | f 810 461 303
2630 | f 810 218 461
2631 | f 63 461 218
2632 | f 218 859 63
2633 | f 461 63 992
2634 | f 455 859 812
2635 | f 63 455 992
2636 | f 859 455 63
2637 | f 455 421 738
2638 | f 738 421 336
2639 | f 516 421 789
2640 | f 812 421 455
2641 | f 421 516 336
2642 | f 859 832 812
2643 | f 832 421 812
2644 | f 832 789 421
2645 | f 832 899 789
2646 | f 953 157 528
2647 | f 882 528 775
2648 | f 157 584 528
2649 | f 528 842 775
2650 | f 528 584 842
2651 | f 624 228 831
2652 | f 624 831 157
2653 | f 157 831 584
2654 | f 842 585 313
2655 | f 584 585 842
2656 | f 831 834 584
2657 | f 834 585 584
2658 | f 834 549 585
2659 | f 228 889 831
2660 | f 831 889 834
2661 | f 834 595 549
2662 | f 549 321 1
2663 | f 321 549 595
2664 | f 595 840 321
2665 | f 228 517 889
2666 | f 834 889 595
2667 | f 517 792 889
2668 | f 889 792 595
2669 | f 792 405 595
2670 | f 405 840 595
2671 | f 227 442 792
2672 | f 792 442 405
2673 | f 227 222 442
2674 | f 442 188 405
2675 | f 405 80 840
2676 | f 549 1 585
2677 | f 1 321 899
2678 | f 321 387 899
2679 | f 321 165 387
2680 | f 861 648 163
2681 | f 47 648 861
2682 | f 648 849 163
2683 | f 59 163 209
2684 | f 59 209 435
2685 | f 648 420 849
2686 | f 648 308 420
2687 | f 163 849 209
2688 | f 435 209 31
2689 | f 209 849 31
2690 | f 849 420 31
2691 | f 988 435 821
2692 | f 435 506 821
2693 | f 81 821 58
2694 | f 821 506 58
2695 | f 435 156 506
2696 | f 81 58 566
2697 | f 566 270 915
2698 | f 58 670 566
2699 | f 58 506 670
2700 | f 435 31 156
2701 | f 156 289 506
2702 | f 506 289 670
2703 | f 270 566 475
2704 | f 566 670 475
2705 | f 156 97 289
2706 | f 289 97 573
2707 | f 420 308 527
2708 | f 308 220 527
2709 | f 31 420 880
2710 | f 420 527 880
2711 | f 220 672 340
2712 | f 283 672 220
2713 | f 220 340 527
2714 | f 527 340 880
2715 | f 340 933 880
2716 | f 672 322 340
2717 | f 340 322 933
2718 | f 31 880 304
2719 | f 31 304 156
2720 | f 322 740 933
2721 | f 322 678 740
2722 | f 304 880 938
2723 | f 156 304 97
2724 | f 304 938 97
2725 | f 880 933 373
2726 | f 933 740 373
2727 | f 880 86 938
2728 | f 880 373 86
2729 | f 678 378 740
2730 | f 740 378 373
2731 | f 678 968 378
2732 | f 378 968 826
2733 | f 938 86 887
2734 | f 86 681 887
2735 | f 86 373 378
2736 | f 378 826 681
2737 | f 378 681 86
2738 | f 826 968 431
2739 | f 270 339 577
2740 | f 270 577 263
2741 | f 270 475 339
2742 | f 475 670 273
2743 | f 339 537 577
2744 | f 864 208 619
2745 | f 864 263 208
2746 | f 619 759 222
2747 | f 577 208 263
2748 | f 670 573 273
2749 | f 475 273 473
2750 | f 475 473 339
2751 | f 670 289 573
2752 | f 273 389 473
2753 | f 273 573 389
2754 | f 537 339 504
2755 | f 339 473 504
2756 | f 473 389 504
2757 | f 577 755 208
2758 | f 389 714 504
2759 | f 619 508 759
2760 | f 619 208 508
2761 | f 208 755 508
2762 | f 577 217 755
2763 | f 537 217 577
2764 | f 537 504 217
2765 | f 504 109 217
2766 | f 755 217 88
2767 | f 755 88 508
2768 | f 573 97 836
2769 | f 97 938 707
2770 | f 97 707 836
2771 | f 573 836 389
2772 | f 389 836 714
2773 | f 836 154 714
2774 | f 836 707 154
2775 | f 707 323 154
2776 | f 154 699 914
2777 | f 938 887 707
2778 | f 681 826 887
2779 | f 887 826 964
2780 | f 707 887 323
2781 | f 826 572 964
2782 | f 431 572 826
2783 | f 964 572 79
2784 | f 89 272 952
2785 | f 572 272 79
2786 | f 887 964 323
2787 | f 964 79 323
2788 | f 323 79 699
2789 | f 323 699 154
2790 | f 699 79 873
2791 | f 914 873 806
2792 | f 914 699 873
2793 | f 89 543 873
2794 | f 79 272 89
2795 | f 873 79 89
2796 | f 504 714 109
2797 | f 217 109 88
2798 | f 109 460 754
2799 | f 88 109 754
2800 | f 754 828 88
2801 | f 109 154 460
2802 | f 714 154 109
2803 | f 754 806 828
2804 | f 460 806 754
2805 | f 154 914 460
2806 | f 914 806 460
2807 | f 806 118 729
2808 | f 873 118 806
2809 | f 35 614 322
2810 | f 672 35 322
2811 | f 614 170 322
2812 | f 614 366 170
2813 | f 170 366 678
2814 | f 170 678 322
2815 | f 366 293 678
2816 | f 366 530 293
2817 | f 161 293 277
2818 | f 530 277 293
2819 | f 530 675 277
2820 | f 675 744 277
2821 | f 394 858 744
2822 | f 631 18 366
2823 | f 631 438 18
2824 | f 18 530 366
2825 | f 18 438 530
2826 | f 530 438 675
2827 | f 438 20 675
2828 | f 20 214 456
2829 | f 20 456 394
2830 | f 675 20 744
2831 | f 20 394 744
2832 | f 678 293 968
2833 | f 293 161 968
2834 | f 955 858 952
2835 | f 968 161 955
2836 | f 161 277 955
2837 | f 277 744 955
2838 | f 955 744 858
2839 | f 968 955 431
2840 | f 431 955 934
2841 | f 572 431 272
2842 | f 934 955 952
2843 | f 952 858 457
2844 | f 934 272 431
2845 | f 934 952 272
2846 | f 387 399 899
2847 | f 387 210 399
2848 | f 399 210 318
2849 | f 210 876 318
2850 | f 251 318 603
2851 | f 876 603 318
2852 | f 603 184 717
2853 | f 385 848 477
2854 | f 347 17 777
2855 | f 477 17 347
2856 | f 418 477 347
2857 | f 17 477 746
2858 | f 848 746 477
2859 | f 848 276 746
2860 | f 777 17 555
2861 | f 746 555 17
2862 | f 746 3 555
2863 | f 789 318 516
2864 | f 318 385 516
2865 | f 385 418 516
2866 | f 516 418 336
2867 | f 418 385 477
2868 | f 385 251 848
2869 | f 899 399 789
2870 | f 399 318 789
2871 | f 318 251 385
2872 | f 251 717 848
2873 | f 276 848 717
2874 | f 717 802 276
2875 | f 251 603 717
2876 | f 726 815 879
2877 | f 184 802 717
2878 | f 184 879 802
2879 | f 184 976 879
2880 | f 751 526 332
2881 | f 332 202 561
2882 | f 561 847 332
2883 | f 553 847 561
2884 | f 553 155 847
2885 | f 751 332 867
2886 | f 726 879 155
2887 | f 879 867 155
2888 | f 867 332 155
2889 | f 847 155 332
2890 | f 332 526 202
2891 | f 746 496 3
2892 | f 536 552 793
2893 | f 496 536 3
2894 | f 552 536 496
2895 | f 496 276 802
2896 | f 496 802 815
2897 | f 746 276 496
2898 | f 815 552 496
2899 | f 989 793 552
2900 | f 552 560 989
2901 | f 213 930 560
2902 | f 930 989 560
2903 | f 552 815 560
2904 | f 560 815 155
2905 | f 553 560 155
2906 | f 213 560 553
2907 | f 553 561 213
2908 | f 879 815 802
2909 | f 815 726 155
2910 | f 222 424 442
2911 | f 442 424 188
2912 | f 222 759 424
2913 | f 80 405 96
2914 | f 188 96 405
2915 | f 424 838 188
2916 | f 321 840 80
2917 | f 80 96 612
2918 | f 188 939 96
2919 | f 838 939 188
2920 | f 96 939 612
2921 | f 80 165 321
2922 | f 387 165 210
2923 | f 80 612 165
2924 | f 165 612 210
2925 | f 612 876 210
2926 | f 939 490 612
2927 | f 612 490 876
2928 | f 838 641 939
2929 | f 939 641 490
2930 | f 641 180 490
2931 | f 641 388 180
2932 | f 388 550 180
2933 | f 603 876 920
2934 | f 603 920 184
2935 | f 184 920 976
2936 | f 490 920 876
2937 | f 920 490 180
2938 | f 180 796 920
2939 | f 920 796 976
2940 | f 424 759 838
2941 | f 759 185 838
2942 | f 759 508 185
2943 | f 185 641 838
2944 | f 185 854 641
2945 | f 508 507 185
2946 | f 185 507 854
2947 | f 854 507 388
2948 | f 507 440 388
2949 | f 507 935 440
2950 | f 508 88 507
2951 | f 854 388 641
2952 | f 796 180 550
2953 | f 34 796 550
2954 | f 976 34 338
2955 | f 34 976 796
2956 | f 338 34 329
2957 | f 958 329 34
2958 | f 879 338 867
2959 | f 879 976 338
2960 | f 526 751 320
2961 | f 266 89 917
2962 | f 917 526 266
2963 | f 118 873 543
2964 | f 118 320 329
2965 | f 118 543 320
2966 | f 338 329 751
2967 | f 329 320 751
2968 | f 89 266 543
2969 | f 266 526 320
2970 | f 320 543 266
2971 | f 751 867 338
2972 | f 828 729 440
2973 | f 440 729 958
2974 | f 88 935 507
2975 | f 88 828 935
2976 | f 440 935 828
2977 | f 440 550 388
2978 | f 958 34 550
2979 | f 440 958 550
2980 | f 806 729 828
2981 | f 729 329 958
2982 | f 329 729 118
2983 | f 394 813 858
2984 | f 756 202 608
2985 | f 813 756 608
2986 | f 679 27 756
2987 | f 141 561 27
2988 | f 561 756 27
2989 | f 202 756 561
2990 | f 394 456 813
2991 | f 100 15 841
2992 | f 756 15 679
2993 | f 841 813 456
2994 | f 841 15 813
2995 | f 15 756 813
2996 | f 608 202 457
2997 | f 813 457 858
2998 | f 813 608 457
2999 | f 457 917 952
3000 | f 457 526 917
3001 | f 457 202 526
3002 | f 141 213 561
3003 | f 952 917 89
--------------------------------------------------------------------------------
/data/objs/tetrahedron.obj:
--------------------------------------------------------------------------------
1 | # WaveFront *.obj file (generated by Cinema 4D)
2 |
3 | v 81.649658203125 -33.33333206176758 -47.14045333862305
4 | v -81.649658203125 -33.33333206176758 -47.14045333862305
5 | v 0 -33.33333206176758 94.28090667724609
6 | v 0 100 0
7 | # 4 vertices
8 |
9 | g Sphere
10 | usemtl default
11 | f 3 2 1
12 | f 2 4 1
13 | f 3 4 2
14 | f 1 4 3
15 |
16 |
--------------------------------------------------------------------------------
/data/point_clouds/ankylosaurus_settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "point_cloud": "data/point_clouds/ankylosaurus.pwn"
3 | }
--------------------------------------------------------------------------------
/data/point_clouds/bull_settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "point_cloud": "data/point_clouds/bull.pwn"
3 | }
--------------------------------------------------------------------------------
/data/point_clouds/elephant_settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "point_cloud": "data/point_clouds/elephant.pwn"
3 | }
--------------------------------------------------------------------------------
/data/point_clouds/giraffe_settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "point_cloud": "data/point_clouds/giraffe.pwn"
3 | }
--------------------------------------------------------------------------------
/data/point_clouds/guitar_settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "point_cloud": "data/point_clouds/guitar.pwn"
3 | }
--------------------------------------------------------------------------------
/data/point_clouds/guitar_settings_pooling.json:
--------------------------------------------------------------------------------
1 | {
2 | "point_cloud": "data/point_clouds/guitar.pwn",
3 | "pooling": [0.9, 0.8, 0.7, 0.6, 0.5, 0.4]
4 | }
--------------------------------------------------------------------------------
/data/point_clouds/hand_settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "point_cloud": "data/point_clouds/hand.pwn"
3 | }
--------------------------------------------------------------------------------
/data/point_clouds/tiki_settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "point_cloud": "data/point_clouds/tiki.pwn"
3 | }
--------------------------------------------------------------------------------
/data/point_clouds/triceratops_settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "point_cloud": "data/point_clouds/triceratops.pwn"
3 | }
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | absl-py==0.11.0
2 | appdirs==1.4.4
3 | astunparse==1.6.3
4 | attrs==20.3.0
5 | black==20.8b1
6 | cachetools==4.1.1
7 | certifi==2020.11.8
8 | chardet==3.0.4
9 | click==7.1.2
10 | cloudpickle==1.6.0
11 | colorama==0.4.4
12 | cycler==0.10.0
13 | decorator==4.4.2
14 | dill==0.3.3
15 | dm-tree==0.1.5
16 | future==0.18.2
17 | gast==0.3.3
18 | google-auth==1.23.0
19 | google-auth-oauthlib==0.4.2
20 | google-pasta==0.2.0
21 | googleapis-common-protos==1.52.0
22 | grpcio==1.33.2
23 | gviz-api==1.9.0
24 | h5py==2.10.0
25 | idna==2.10
26 | importlib-resources==3.3.0
27 | Keras-Preprocessing==1.1.2
28 | kiwisolver==1.3.1
29 | Markdown==3.3.3
30 | matplotlib==3.3.3
31 | mypy-extensions==0.4.3
32 | networkx==2.5
33 | numpy==1.18.5
34 | oauthlib==3.1.0
35 | OpenEXR==1.3.2
36 | opt-einsum==3.3.0
37 | pathspec==0.8.1
38 | Pillow==8.0.1
39 | promise==2.3
40 | protobuf==3.14.0
41 | psutil==5.7.3
42 | pyasn1==0.4.8
43 | pyasn1-modules==0.2.8
44 | pyparsing==2.4.7
45 | python-dateutil==2.8.1
46 | regex==2020.11.13
47 | requests==2.25.0
48 | requests-oauthlib==1.3.0
49 | rsa==4.6
50 | scipy==1.5.4
51 | six==1.15.0
52 | tensorboard==2.4.0
53 | tensorboard-plugin-profile==2.3.0
54 | tensorboard-plugin-wit==1.7.0
55 | tensorflow==2.3.1
56 | tensorflow-datasets==4.1.0
57 | tensorflow-estimator==2.3.0
58 | tensorflow-graphics==2020.5.20
59 | tensorflow-metadata==0.25.0
60 | tensorflow-probability==0.11.1
61 | termcolor==1.1.0
62 | toml==0.10.2
63 | tqdm==4.53.0
64 | trimesh==3.8.12
65 | typed-ast==1.4.1
66 | typing-extensions==3.7.4.3
67 | urllib3==1.26.2
68 | Werkzeug==1.0.1
69 | wrapt==1.12.1
70 |
--------------------------------------------------------------------------------
/source/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/__init__.py
--------------------------------------------------------------------------------
/source/layers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/layers/__init__.py
--------------------------------------------------------------------------------
/source/layers/composite/ConvolutionSequence.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 | from typing import List
3 | from tensorflow.keras.layers import Layer
4 | from ...mesh.Mesh import Mesh
5 | from ..convolution.MeshConvolution import MeshConvolution
6 | from ..feature import features_valid
7 |
8 |
9 | class ConvolutionSequence(Layer):
10 | """A ConvolutionSequence is simply a number of sequential convolutions."""
11 |
12 | convolutions: List[MeshConvolution]
13 |
14 | def __init__(self, out_channels: int, num_convolutions: int):
15 | Layer.__init__(self)
16 |
17 | # Create num_convolutions MeshConvolution layers.
18 | self.convolutions = [
19 | MeshConvolution(out_channels) for _ in range(num_convolutions)
20 | ]
21 |
22 | def call(self, mesh: Mesh, features: tf.Tensor) -> tf.Tensor:
23 | assert features_valid(mesh, features)
24 |
25 | for convolution in self.convolutions:
26 | features = convolution(mesh, features)
27 |
28 | return features
29 |
--------------------------------------------------------------------------------
/source/layers/composite/Decoder.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 | from typing import List, Optional
3 | from tensorflow.keras.layers import Layer
4 | from .UpConvolution import UpConvolution
5 | from ...mesh.Mesh import Mesh
6 | from ..feature import features_valid
7 | from ..pooling.CollapseSnapshot import CollapseSnapshot
8 |
9 |
10 | class Decoder(Layer):
11 | """This combines several UpConvolution layers."""
12 |
13 | up_convolutions: List[UpConvolution]
14 |
15 | def __init__(
16 | self,
17 | out_channels: List[int],
18 | convolutions_per_sequence: int,
19 | num_sequences_per_down_convolution: int,
20 | leaky_relu_alpha: float,
21 | encoder_pool_targets: List[Optional[int]],
22 | ):
23 | Layer.__init__(self)
24 |
25 | assert len(out_channels) == len(encoder_pool_targets)
26 |
27 | needs_unpool = [x != None for x in reversed(encoder_pool_targets)]
28 | self.up_convolutions = [
29 | UpConvolution(
30 | out_channels[i],
31 | convolutions_per_sequence,
32 | num_sequences_per_down_convolution,
33 | leaky_relu_alpha,
34 | needs_unpool[i],
35 | )
36 | for i in range(len(encoder_pool_targets))
37 | ]
38 |
39 | def call(
40 | self, mesh: Mesh, features: tf.Tensor, snapshots: CollapseSnapshot
41 | ) -> tf.Tensor:
42 | assert features_valid(mesh, features)
43 |
44 | snapshots = reversed(snapshots)
45 | for up_convolution, snapshot in zip(self.up_convolutions, snapshots):
46 | features = up_convolution(mesh, features, snapshot)
47 |
48 | return features
49 |
--------------------------------------------------------------------------------
/source/layers/composite/DownConvolution.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 | from typing import List, Optional
3 | from tensorflow.keras.layers import Layer, LeakyReLU, BatchNormalization
4 | from ...mesh.Mesh import Mesh
5 | from ..feature import features_valid
6 | from ..pooling.MeshPool import MeshPool
7 | from .ConvolutionSequence import ConvolutionSequence
8 |
9 |
10 | class DownConvolution(Layer):
11 | """A DownConvolution combines several skip-connected ConvolutionSequence
12 | blocks separated by nonlinearities and batch normalization. It can
13 | optionally include a pooling step at the end.
14 | """
15 |
16 | convolutions: List[ConvolutionSequence]
17 | mesh_pool: Optional[MeshPool]
18 | batch_normalizations: List[BatchNormalization]
19 | leaky_relu: LeakyReLU
20 |
21 | def __init__(
22 | self,
23 | out_channels: int,
24 | convolutions_per_sequence: int,
25 | num_sequences: int,
26 | leaky_relu_alpha: float,
27 | pool_target: Optional[int],
28 | ):
29 | Layer.__init__(self)
30 | self.convolutions = [
31 | ConvolutionSequence(out_channels, convolutions_per_sequence)
32 | for _ in range(num_sequences)
33 | ]
34 | self.leaky_relu = LeakyReLU(leaky_relu_alpha)
35 | self.batch_normalizations = [BatchNormalization() for _ in range(num_sequences)]
36 | self.mesh_pool = None if pool_target is None else MeshPool(pool_target)
37 |
38 | def call(self, mesh: Mesh, features: tf.Tensor) -> tf.Tensor:
39 | assert features_valid(mesh, features)
40 |
41 | # Run through the first ConvolutionSequence.
42 | out_features = self.convolutions[0](mesh, features)
43 | out_features = self.leaky_relu(out_features)
44 | out_features = self.batch_normalizations[0](out_features, training=True)
45 |
46 | # Run the remaining ConvolutionSequences with skip connections.
47 | in_features = out_features
48 | for i in range(1, len(self.convolutions)):
49 | # Create out_features using in_features.
50 | out_features = self.convolutions[i](mesh, in_features)
51 | out_features = self.leaky_relu(out_features)
52 | out_features = self.batch_normalizations[i](out_features, training=True)
53 |
54 | # Add the skip connections.
55 | out_features += in_features
56 | in_features = out_features
57 |
58 | # Optionally run pooling.
59 | if self.mesh_pool is not None:
60 | return self.mesh_pool(mesh, out_features)
61 | return out_features, None
62 |
--------------------------------------------------------------------------------
/source/layers/composite/Encoder.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 | from typing import List, Optional
3 | from tensorflow.keras.layers import Layer
4 | from .DownConvolution import DownConvolution
5 | from ...mesh.Mesh import Mesh
6 | from ..feature import features_valid
7 |
8 |
9 | class Encoder(Layer):
10 | """This combines several DownConvolution layers."""
11 |
12 | down_convolutions: List[DownConvolution]
13 |
14 | def __init__(
15 | self,
16 | out_channels: List[int],
17 | convolutions_per_sequence: int,
18 | num_sequences_per_down_convolution: int,
19 | leaky_relu_alpha: float,
20 | pool_targets: List[Optional[int]],
21 | ):
22 | Layer.__init__(self)
23 |
24 | assert len(out_channels) == len(pool_targets)
25 | for i in range(1, len(pool_targets)):
26 | assert pool_targets[i] is None or pool_targets[i] < pool_targets[i - 1]
27 |
28 | self.down_convolutions = [
29 | DownConvolution(
30 | out_channels[i],
31 | convolutions_per_sequence,
32 | num_sequences_per_down_convolution,
33 | leaky_relu_alpha,
34 | pool_targets[i],
35 | )
36 | for i in range(len(pool_targets))
37 | ]
38 |
39 | def call(self, mesh: Mesh, features: tf.Tensor) -> tf.Tensor:
40 | assert features_valid(mesh, features)
41 |
42 | snapshots = []
43 | for down_convolution in self.down_convolutions:
44 | features, snapshot = down_convolution(mesh, features)
45 | snapshots.append(snapshot)
46 |
47 | return features, snapshots
48 |
--------------------------------------------------------------------------------
/source/layers/composite/UpConvolution.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 | from typing import List, Optional
3 | from tensorflow.keras.layers import Layer, LeakyReLU, BatchNormalization
4 | from ...mesh.Mesh import Mesh
5 | from ..feature import features_valid
6 | from ..pooling.MeshUnpool import MeshUnpool
7 | from ..pooling.CollapseSnapshot import CollapseSnapshot
8 | from .ConvolutionSequence import ConvolutionSequence
9 |
10 |
11 | class UpConvolution(Layer):
12 | """An UpConvolution combines several skip-connected ConvolutionSequence
13 | blocks separated by nonlinearities and batch normalization. It can
14 | optionally include an unpooling step at the end.
15 | """
16 |
17 | convolutions: List[ConvolutionSequence]
18 | mesh_unpool: Optional[MeshUnpool]
19 | batch_normalizations: List[BatchNormalization]
20 | leaky_relu: LeakyReLU
21 |
22 | def __init__(
23 | self,
24 | out_channels: int,
25 | convolutions_per_sequence: int,
26 | num_sequences: int,
27 | leaky_relu_alpha: float,
28 | unpool: bool,
29 | ):
30 | Layer.__init__(self)
31 | self.convolutions = [
32 | ConvolutionSequence(out_channels, convolutions_per_sequence)
33 | for _ in range(num_sequences)
34 | ]
35 | self.leaky_relu = LeakyReLU(leaky_relu_alpha)
36 | self.batch_normalizations = [BatchNormalization() for _ in range(num_sequences)]
37 | self.mesh_unpool = MeshUnpool() if unpool else None
38 |
39 | def call(
40 | self, mesh: Mesh, features: tf.Tensor, snapshot: CollapseSnapshot
41 | ) -> tf.Tensor:
42 | assert features_valid(mesh, features)
43 |
44 | # Run through the first ConvolutionSequence.
45 | out_features = self.convolutions[0](mesh, features)
46 | out_features = self.leaky_relu(out_features)
47 | out_features = self.batch_normalizations[0](out_features, training=True)
48 |
49 | # Run the remaining ConvolutionSequences with skip connections.
50 | in_features = out_features
51 | for i in range(1, len(self.convolutions)):
52 | # Create out_features using in_features.
53 | out_features = self.convolutions[i](mesh, in_features)
54 | out_features = self.leaky_relu(out_features)
55 | out_features = self.batch_normalizations[i](out_features, training=True)
56 |
57 | # Add the skip connections.
58 | out_features += in_features
59 | in_features = out_features
60 |
61 | # Optionally run unpooling.
62 | if self.mesh_unpool is not None:
63 | return self.mesh_unpool(mesh, out_features, snapshot)
64 | return out_features
65 |
--------------------------------------------------------------------------------
/source/layers/composite/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/layers/composite/__init__.py
--------------------------------------------------------------------------------
/source/layers/composite/__tests__/TestConvolutionSequence.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import trimesh
3 | import tensorflow as tf
4 | import numpy as np
5 | from ....mesh.Mesh import Mesh
6 | from ..ConvolutionSequence import ConvolutionSequence
7 |
8 |
9 | class TestConvolutionSequence(unittest.TestCase):
10 | def setUp(self) -> None:
11 | with open("data/objs/icosahedron.obj", "r") as f:
12 | mesh = trimesh.exchange.obj.load_obj(f)
13 | self.mesh = Mesh(mesh["vertices"], mesh["faces"])
14 |
15 | def test_output_shape(self):
16 | num_edges = self.mesh.edges.shape[0]
17 | sequence = ConvolutionSequence(11, 3)
18 |
19 | initial_feature_values = np.random.random((num_edges, 3))
20 | features = tf.Variable(initial_feature_values, dtype=tf.float32)
21 |
22 | result = sequence(self.mesh, features)
23 |
24 | self.assertEqual(result.shape, (30, 11))
25 |
--------------------------------------------------------------------------------
/source/layers/composite/__tests__/TestDecoder.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import trimesh
3 | import tensorflow as tf
4 | import numpy as np
5 | from ....mesh.Mesh import Mesh
6 | from ..Encoder import Encoder
7 | from ..Decoder import Decoder
8 |
9 |
10 | class TestDecoder(unittest.TestCase):
11 | def setUp(self) -> None:
12 | with open("data/objs/icosahedron.obj", "r") as f:
13 | mesh = trimesh.exchange.obj.load_obj(f)
14 | self.mesh = Mesh(mesh["vertices"], mesh["faces"])
15 |
16 | def test_output_shape(self):
17 | num_edges = self.mesh.edges.shape[0]
18 |
19 | encoder = Encoder((12, 24, 48), 3, 4, 0.1, (27, 24, None))
20 | decoder = Decoder((24, 12, 6), 3, 4, 0.1, (27, 24, None))
21 | initial_feature_values = np.random.random((num_edges, 6))
22 | in_features = tf.Variable(initial_feature_values, dtype=tf.float32)
23 |
24 | encoding, snapshots = encoder(self.mesh, in_features)
25 | out_features = decoder(self.mesh, encoding, snapshots)
26 |
27 | self.assertEqual(out_features.shape, (30, 6))
28 |
--------------------------------------------------------------------------------
/source/layers/composite/__tests__/TestDownConvolution.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import trimesh
3 | import tensorflow as tf
4 | import numpy as np
5 | from ....mesh.Mesh import Mesh
6 | from ..DownConvolution import DownConvolution
7 |
8 |
9 | class TestDownConvolution(unittest.TestCase):
10 | def setUp(self) -> None:
11 | with open("data/objs/icosahedron.obj", "r") as f:
12 | mesh = trimesh.exchange.obj.load_obj(f)
13 | self.mesh = Mesh(mesh["vertices"], mesh["faces"])
14 |
15 | def test_output_shape_with_pooling(self):
16 | num_edges = self.mesh.edges.shape[0]
17 | down = DownConvolution(17, 3, 4, 0.1, 18)
18 | initial_feature_values = np.random.random((num_edges, 6))
19 | in_features = tf.Variable(initial_feature_values, dtype=tf.float32)
20 |
21 | out_features, _ = down(self.mesh, in_features)
22 |
23 | self.assertEqual(out_features.shape, (18, 17))
24 |
25 | def test_output_shape_without_pooling(self):
26 | num_edges = self.mesh.edges.shape[0]
27 | down = DownConvolution(31, 3, 4, 0.1, None)
28 | initial_feature_values = np.random.random((num_edges, 2))
29 | in_features = tf.Variable(initial_feature_values, dtype=tf.float32)
30 |
31 | out_features, _ = down(self.mesh, in_features)
32 |
33 | self.assertEqual(out_features.shape, (30, 31))
34 |
--------------------------------------------------------------------------------
/source/layers/composite/__tests__/TestEncoder.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import trimesh
3 | import tensorflow as tf
4 | import numpy as np
5 | from ....mesh.Mesh import Mesh
6 | from ..Encoder import Encoder
7 |
8 |
9 | class TestEncoder(unittest.TestCase):
10 | def setUp(self) -> None:
11 | with open("data/objs/icosahedron.obj", "r") as f:
12 | mesh = trimesh.exchange.obj.load_obj(f)
13 | self.mesh = Mesh(mesh["vertices"], mesh["faces"])
14 |
15 | def test_output_shape(self):
16 | num_edges = self.mesh.edges.shape[0]
17 | encoder = Encoder((12, 24, 48), 3, 4, 0.1, (27, 24, None))
18 | initial_feature_values = np.random.random((num_edges, 6))
19 | in_features = tf.Variable(initial_feature_values, dtype=tf.float32)
20 |
21 | out_features, _ = encoder(self.mesh, in_features)
22 |
23 | self.assertEqual(out_features.shape, (24, 48))
24 |
--------------------------------------------------------------------------------
/source/layers/composite/__tests__/TestUpConvolution.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import trimesh
3 | import tensorflow as tf
4 | import numpy as np
5 | from ....mesh.Mesh import Mesh
6 | from ..DownConvolution import DownConvolution
7 | from ..UpConvolution import UpConvolution
8 |
9 |
10 | class TestUpConvolution(unittest.TestCase):
11 | def setUp(self) -> None:
12 | with open("data/objs/icosahedron.obj", "r") as f:
13 | mesh = trimesh.exchange.obj.load_obj(f)
14 | self.mesh = Mesh(mesh["vertices"], mesh["faces"])
15 |
16 | def test_output_shape_with_pooling(self):
17 | num_edges = self.mesh.edges.shape[0]
18 | down = DownConvolution(17, 3, 4, 0.1, 18)
19 | up = UpConvolution(6, 3, 4, 0.1, True)
20 | initial_feature_values = np.random.random((num_edges, 6))
21 | in_features = tf.Variable(initial_feature_values, dtype=tf.float32)
22 |
23 | encoding, snapshot = down(self.mesh, in_features)
24 | out_features = up(self.mesh, encoding, snapshot)
25 |
26 | self.assertEqual(out_features.shape, (30, 6))
27 |
28 | def test_output_shape_without_pooling(self):
29 | num_edges = self.mesh.edges.shape[0]
30 | down = DownConvolution(17, 3, 4, 0.1, None)
31 | up = UpConvolution(6, 3, 4, 0.1, False)
32 | initial_feature_values = np.random.random((num_edges, 6))
33 | in_features = tf.Variable(initial_feature_values, dtype=tf.float32)
34 |
35 | encoding, snapshot = down(self.mesh, in_features)
36 | out_features = up(self.mesh, encoding, snapshot)
37 |
38 | self.assertEqual(out_features.shape, (30, 6))
39 |
--------------------------------------------------------------------------------
/source/layers/composite/__tests__/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/layers/composite/__tests__/__init__.py
--------------------------------------------------------------------------------
/source/layers/convolution/MeshConvolution.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 | import numpy as np
3 | from tensorflow.keras.layers import Layer
4 | from ...mesh.Mesh import Mesh
5 | from ..feature import features_valid
6 |
7 |
8 | class MeshConvolution(Layer):
9 | """MeshConvolution implements MeshCNN's mesh convolution operation by
10 | computing convolution between edges and 4 incident (1-ring) edge neighbors.
11 | """
12 |
13 | def __init__(
14 | self,
15 | out_channels,
16 | kernel_initializer="glorot_uniform",
17 | bias_initializer="zeros",
18 | ):
19 | Layer.__init__(self)
20 | self.conv = tf.keras.layers.Conv2D(
21 | filters=out_channels,
22 | kernel_size=(1, 5),
23 | kernel_initializer=kernel_initializer,
24 | bias_initializer=bias_initializer,
25 | )
26 |
27 | def call(self, mesh: Mesh, features: tf.Tensor) -> tf.Tensor:
28 | """Compute convolution between edges and 4 incident (1-ring) edge
29 | neighbors using standard Conv2D.
30 | """
31 | assert features_valid(mesh, features)
32 | feature_image = self.create_feature_image(mesh, features)
33 | edge_feats = self.conv(feature_image)
34 | return edge_feats[0, :, 0, :]
35 |
36 | def create_feature_image(self, mesh: Mesh, features: tf.Tensor) -> tf.Tensor:
37 | """Using the connectivity information in mesh.edge_to_neighbor, create a
38 | an "image" that can be used by conv2d. The image's dimensions shape is
39 | (1, num_edges, 5, feature_size). Think of feature_size as in_channels.
40 | """
41 |
42 | # Create neighborhoods of the original edge plus its neighbors.
43 | neighborhoods = np.concatenate(
44 | [np.arange(mesh.edges.shape[0])[:, None], mesh.edge_to_neighbors], axis=1
45 | )
46 |
47 | # Gather features into the shape (num_edges, 5, feature_size).
48 | f = tf.gather(features, neighborhoods, axis=0)
49 |
50 | # Apply the symmetric functions to make an convolution equivariant.
51 | x_1 = f[:, 1, :] + f[:, 3, :]
52 | x_2 = f[:, 2, :] + f[:, 4, :]
53 | x_3 = tf.math.abs(f[:, 1, :] - f[:, 3, :])
54 | x_4 = tf.math.abs(f[:, 2, :] - f[:, 4, :])
55 | image = tf.stack([f[:, 0, :], x_1, x_2, x_3, x_4], axis=1)
56 |
57 | # Add a fake batch dimension.
58 | return image[None, :, :, :]
59 |
--------------------------------------------------------------------------------
/source/layers/convolution/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/layers/convolution/__init__.py
--------------------------------------------------------------------------------
/source/layers/convolution/__tests__/TestMeshConvolution.py:
--------------------------------------------------------------------------------
1 | from source.mesh.MeshChecker import MeshChecker
2 | import unittest
3 | import trimesh
4 | import tensorflow as tf
5 | import numpy as np
6 | from ....mesh.Mesh import Mesh
7 | from ..MeshConvolution import MeshConvolution
8 |
9 |
10 | class TestMeshConvolution(unittest.TestCase):
11 | def setUp(self) -> None:
12 | with open("data/objs/icosahedron.obj", "r") as f:
13 | mesh = trimesh.exchange.obj.load_obj(f)
14 | self.mesh = Mesh(mesh["vertices"], mesh["faces"])
15 |
16 | def test_output_shape(self):
17 | num_edges = self.mesh.edges.shape[0]
18 | self.mesh_convolution = MeshConvolution(2)
19 |
20 | initial_feature_values = np.zeros((num_edges, 3))
21 | initial_feature_values[:, :] = np.arange(num_edges)[:, None]
22 |
23 | features = tf.Variable(initial_feature_values, dtype=tf.float32)
24 | result = self.mesh_convolution(self.mesh, features)
25 |
26 | self.assertEqual(result.shape, (30, 2))
27 |
--------------------------------------------------------------------------------
/source/layers/convolution/__tests__/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/layers/convolution/__tests__/__init__.py
--------------------------------------------------------------------------------
/source/layers/feature.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 | from ..mesh.Mesh import Mesh
3 |
4 |
5 | def features_valid(mesh: Mesh, features: tf.Tensor) -> bool:
6 | """Check whether a feature tensor is compatible with the specified mesh."""
7 |
8 | # A feature tensor's shape should be (feature_length, num_edges).
9 | return tf.rank(features) == 2 and features.shape[0] == mesh.edges.shape[0]
--------------------------------------------------------------------------------
/source/layers/pooling/CollapseSnapshot.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, List, Set
2 | import numpy as np
3 | from ...mesh.Mesh import Mesh
4 | from ...mesh.EdgeConnection import EdgeConnection
5 |
6 |
7 | class CollapseSnapshot:
8 | """This holds information about which edges in a mesh were collapsed. It's
9 | used to make the pooling operation reversible.
10 | """
11 |
12 | # This is the mesh this CollapseSnapshot references.
13 | mesh: Mesh
14 |
15 | # This square matrix with shape (num_edges, num_edges) holds information
16 | # about how the mesh's edges have been collapsed. A nonzero entry at (i, j)
17 | # indicates that the ith edge is the parent of the jth edge. Note that all
18 | # edges start parented to themselves.
19 | relationships: np.ndarray
20 |
21 | # These are copies of the mesh's fields.
22 | vertices: np.ndarray # (num_vertices, 3)
23 | edges: np.ndarray # (num_edges, 2)
24 | edge_to_neighbors: np.ndarray # (num_edges, 4)
25 | edge_lookup: np.ndarray # (num_edges, 4)
26 | vertex_to_edges: List[Optional[Set[EdgeConnection]]]
27 |
28 | def __init__(self, mesh: Mesh) -> None:
29 | # Assume we're starting with a clean mesh, i.e. no pooling/subdivision
30 | # has occurred.
31 | assert (mesh.edge_mask == True).all()
32 | assert (mesh.vertex_mask == True).all()
33 |
34 | # Save the information needed to reconstruct the mesh.
35 | # Note that this does not include the masks (see above) or the faces.
36 | # The faces are not included because collapse_edge does not modify them.
37 | self.vertices = np.copy(mesh.vertices)
38 | self.edges = np.copy(mesh.edges)
39 | self.edge_to_neighbors = np.copy(mesh.edge_to_neighbors)
40 | self.edge_lookup = np.copy(mesh.edge_lookup)
41 | self.vertex_to_edges = [set(ec) for ec in mesh.vertex_to_edges]
42 |
43 | # The identity matrix parents all edges to themselves.
44 | self.relationships = np.eye(mesh.edges.shape[0])
45 |
46 | def reparent(self, child_edge_key: int, parent_edge_key: int) -> None:
47 | """Reparent the child edge and its children to the parent edge."""
48 | self.relationships[parent_edge_key, :] += self.relationships[child_edge_key, :]
49 | self.relationships[child_edge_key, :] = 0
50 |
51 | def extract_relationships(self):
52 | """Once a number of edges have been reparented, relationships will be a
53 | square matrix with several zero rows. This returns relationships with
54 | its zero rows removed.
55 | """
56 | return self.relationships[np.sum(self.relationships, axis=1) != 0]
--------------------------------------------------------------------------------
/source/layers/pooling/MeshPool.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 | import numpy as np
3 | from tensorflow.keras.layers import Layer
4 | from ...mesh.Mesh import Mesh
5 | from ..feature import features_valid
6 | from .collapse_edge import collapse_edge
7 | from .CollapseSnapshot import CollapseSnapshot
8 |
9 |
10 | class MeshPool(Layer):
11 | """MeshPool implements MeshCNN's mesh pooling. It collapses edges until a
12 | specified number of edges is reached. Each MeshPool should be paired with
13 | a corresponding MeshUnpool. Note that MeshPool's collapse operation does not
14 | update a mesh's faces (i.e. the mesh is valid except for its faces). This is
15 | because the edge structure, which is derived from the face structure, is all
16 | that is needed for pooling and un-pooling to work.
17 | """
18 |
19 | # Edge collapsing continues until the mesh's number of edges is edge_target.
20 | edge_target: int
21 |
22 | def __init__(self, edge_target: int) -> None:
23 | Layer.__init__(self)
24 | self.edge_target = edge_target
25 |
26 | def call(self, mesh: Mesh, features: tf.Tensor):
27 | assert features_valid(mesh, features)
28 |
29 | # Create a snapshot of the mesh's structure.
30 | # This contains all the information needed to undo the pooling (edge
31 | # collapse) operation. It also contains information about how the old
32 | # edges/features are mapped to the new ones in the relationships matrix.
33 | snapshot = CollapseSnapshot(mesh)
34 |
35 | # Sort the features by their L2 norms.
36 | values = tf.norm(features, axis=0)
37 | sorted_edge_keys = tf.argsort(values, direction="DESCENDING")
38 |
39 | # Collapse edges until edge_target is hit.
40 | # Keep track of how edges were merged in the snapshot's relationships
41 | # matrix, which will be used to determine how features will be pooled
42 | # and later unpooled.
43 | for edge_key in sorted_edge_keys:
44 | collapse_edge(mesh, edge_key.numpy(), snapshot)
45 | if mesh.num_edges <= self.edge_target:
46 | break
47 |
48 | # Rebuild the mesh to remove masked vertices and edges.
49 | mesh.collapse_masked_elements()
50 |
51 | # Return both the pooled features and the information needed to unpool.
52 | return self.pool_features(features, snapshot), snapshot
53 |
54 | def pool_features(
55 | self, features: tf.Tensor, snapshot: CollapseSnapshot
56 | ) -> tf.Tensor:
57 | """Use the snapshot's relationships to pool the features. Each new
58 | edge's feature is a weighted average of its children's features.
59 | """
60 | relationships = snapshot.extract_relationships()
61 | weighted = relationships / np.sum(relationships, axis=1)[:, None]
62 | return weighted @ features
63 |
--------------------------------------------------------------------------------
/source/layers/pooling/MeshUnpool.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 | import numpy as np
3 | from tensorflow.keras.layers import Layer
4 | from ...mesh.Mesh import Mesh
5 | from ..feature import features_valid
6 | from .collapse_edge import collapse_edge
7 | from .CollapseSnapshot import CollapseSnapshot
8 |
9 |
10 | class MeshUnpool(Layer):
11 | """MeshUnpool implements MeshCNN's mesh unpooling. It reverses the edge
12 | collapses carried out by MeshPool and distributes each edge's features to
13 | its children in the snapshot's relationships.
14 | """
15 |
16 | def __init__(self) -> None:
17 | Layer.__init__(self)
18 |
19 | def call(self, mesh: Mesh, features: tf.Tensor, snapshot: CollapseSnapshot):
20 | assert features_valid(mesh, features)
21 |
22 | # Restore the mesh's pre-collapse state.
23 | mesh.vertices = snapshot.vertices
24 | mesh.edges = snapshot.edges
25 | mesh.edge_to_neighbors = snapshot.edge_to_neighbors
26 | mesh.edge_lookup = snapshot.edge_lookup
27 | mesh.vertex_to_edges = snapshot.vertex_to_edges
28 | mesh.edge_mask = np.ones((mesh.edges.shape[0]), dtype=np.bool)
29 | mesh.vertex_mask = np.ones((mesh.vertices.shape[0]), dtype=np.bool)
30 | mesh.num_edges = mesh.edges.shape[0]
31 |
32 | # Use the snapshot's relationships to unpool the features.
33 | return self.unpool_features(features, snapshot)
34 |
35 | def unpool_features(
36 | self, features: tf.Tensor, snapshot: CollapseSnapshot
37 | ) -> tf.Tensor:
38 | """Use the snapshot's relationships to unpool the features. Each new
39 | edge's feature is simply its parent's feature.
40 | """
41 | return snapshot.extract_relationships().T @ features
42 |
--------------------------------------------------------------------------------
/source/layers/pooling/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/layers/pooling/__init__.py
--------------------------------------------------------------------------------
/source/layers/pooling/__tests__/TestCollapseEdge.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import trimesh
3 | import numpy as np
4 | from typing import Tuple
5 | from ....mesh.MeshChecker import MeshChecker
6 | from ....mesh.Mesh import Mesh
7 | from ..CollapseSnapshot import CollapseSnapshot
8 | from ..collapse_edge import check_collapse_manifold, collapse_edge
9 |
10 |
11 | class TestCollapseEdge(unittest.TestCase):
12 | def load_obj(_, file_name: str) -> Tuple[np.ndarray]:
13 | with open(file_name, "r") as f:
14 | mesh = trimesh.exchange.obj.load_obj(f)
15 | return mesh["vertices"], mesh["faces"]
16 |
17 | def test_check_collapse_manifold_for_invalid(self):
18 | vertices, faces = self.load_obj("data/objs/tetrahedron.obj")
19 | mesh = Mesh(vertices, faces)
20 | for edge_key in range(mesh.edges.shape[0]):
21 | self.assertFalse(check_collapse_manifold(mesh, edge_key))
22 |
23 | def test_check_collapse_manifold_for_valid(self):
24 | vertices, faces = self.load_obj("data/objs/icosahedron.obj")
25 | mesh = Mesh(vertices, faces)
26 | for edge_key in range(mesh.edges.shape[0]):
27 | self.assertTrue(check_collapse_manifold(mesh, edge_key))
28 |
29 | def test_collapse_individual_icosahedron_edges(self):
30 | vertices, faces = self.load_obj("data/objs/icosahedron.obj")
31 | num_edges = Mesh(vertices, faces).edges.shape[0]
32 | for edge_key in range(num_edges):
33 | mesh = Mesh(vertices, faces)
34 | snapshot = CollapseSnapshot(mesh)
35 | checker = MeshChecker(mesh)
36 | self.assertTrue(collapse_edge(mesh, edge_key, snapshot))
37 | self.assertTrue(checker.check_validity())
38 |
39 | def test_collapse_sequential_icosahedron_edges(self):
40 | vertices, faces = self.load_obj("data/objs/icosahedron.obj")
41 | mesh = Mesh(vertices, faces)
42 | snapshot = CollapseSnapshot(mesh)
43 | checker = MeshChecker(mesh)
44 | edge_key = 0
45 | for _ in range(mesh.edges.shape[0]):
46 | collapse_edge(mesh, edge_key, snapshot)
47 | self.assertTrue(checker.check_validity())
48 | edge_key += 1
49 |
--------------------------------------------------------------------------------
/source/layers/pooling/__tests__/TestMeshPool.py:
--------------------------------------------------------------------------------
1 | from source.mesh.MeshChecker import MeshChecker
2 | import unittest
3 | import trimesh
4 | import tensorflow as tf
5 | import numpy as np
6 | from ....mesh.Mesh import Mesh
7 | from ..MeshPool import MeshPool
8 | from ..CollapseSnapshot import CollapseSnapshot
9 |
10 |
11 | class TestMeshPool(unittest.TestCase):
12 | def setUp(self) -> None:
13 | with open("data/objs/icosahedron.obj", "r") as f:
14 | mesh = trimesh.exchange.obj.load_obj(f)
15 | self.mesh = Mesh(mesh["vertices"], mesh["faces"])
16 |
17 | def test_mesh_remains_valid(self):
18 | num_edges = self.mesh.edges.shape[0]
19 | self.mesh_pool = MeshPool(num_edges - 1)
20 |
21 | initial_feature_values = np.zeros((num_edges, 3))
22 | initial_feature_values[:, :] = np.arange(num_edges)[:, None]
23 |
24 | features = tf.Variable(initial_feature_values, dtype=tf.float32)
25 | self.mesh_pool(self.mesh, features)
26 |
27 | checker = MeshChecker(self.mesh)
28 | self.assertTrue(checker.check_validity())
29 |
30 | def test_weighted_average(self):
31 | num_edges = self.mesh.edges.shape[0]
32 | self.mesh_pool = MeshPool(num_edges // 2)
33 |
34 | features = tf.Variable(np.ones((num_edges, 7)), dtype=tf.float32)
35 | new_features, _ = self.mesh_pool(self.mesh, features)
36 |
37 | self.assertTrue(np.allclose(new_features.numpy(), 1))
38 |
--------------------------------------------------------------------------------
/source/layers/pooling/__tests__/TestMeshUnpool.py:
--------------------------------------------------------------------------------
1 | from source.mesh.MeshChecker import MeshChecker
2 | import unittest
3 | import trimesh
4 | import tensorflow as tf
5 | import numpy as np
6 | from ....mesh.Mesh import Mesh
7 | from ..MeshPool import MeshPool
8 | from ..MeshUnpool import MeshUnpool
9 | from ..CollapseSnapshot import CollapseSnapshot
10 |
11 |
12 | class TestMeshUnpool(unittest.TestCase):
13 | def setUp(self) -> None:
14 | with open("data/objs/icosahedron.obj", "r") as f:
15 | mesh = trimesh.exchange.obj.load_obj(f)
16 | self.mesh = Mesh(mesh["vertices"], mesh["faces"])
17 |
18 | def test_mesh_remains_valid(self):
19 | num_edges = self.mesh.edges.shape[0]
20 | self.mesh_pool = MeshPool(num_edges // 2)
21 | self.mesh_unpool = MeshUnpool()
22 |
23 | original_features = tf.Variable(np.ones((num_edges, 7)), dtype=tf.float32)
24 | pooled_features, snapshot = self.mesh_pool(self.mesh, original_features)
25 | self.mesh_unpool(self.mesh, pooled_features, snapshot)
26 |
27 | checker = MeshChecker(self.mesh)
28 | self.assertTrue(checker.check_validity())
29 |
30 | def test_weighted_average(self):
31 | num_edges = self.mesh.edges.shape[0]
32 | self.mesh_pool = MeshPool(num_edges // 2)
33 | self.mesh_unpool = MeshUnpool()
34 |
35 | original_features = tf.Variable(np.ones((num_edges, 7)), dtype=tf.float32)
36 | pooled_features, snapshot = self.mesh_pool(self.mesh, original_features)
37 | unpooled_features = self.mesh_unpool(self.mesh, pooled_features, snapshot)
38 |
39 | self.assertTrue(
40 | np.allclose(original_features.numpy(), unpooled_features.numpy())
41 | )
42 |
--------------------------------------------------------------------------------
/source/layers/pooling/__tests__/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/layers/pooling/__tests__/__init__.py
--------------------------------------------------------------------------------
/source/layers/pooling/collapse_edge.py:
--------------------------------------------------------------------------------
1 | from ...mesh.Mesh import Mesh
2 | from ...mesh.EdgeConnection import EdgeConnection
3 | from .CollapseSnapshot import CollapseSnapshot
4 |
5 |
6 | def collapse_edge(mesh: Mesh, edge_key: int, snapshot: CollapseSnapshot):
7 | """Collapse an edge (two triangles) and update edge_mask to remove three
8 | edges: the one that's being collapsed and two edges that would overlap
9 | other edges as a result of the collapse. The mesh should be in a
10 | consistent state before and after this is called. Update the provided
11 | CollapseSnapshot with information about which edges have been collapsed.
12 | Note that the collapsed edge is parented to both surviving edges.
13 |
14 | Important: This edge collapse operation does not modify the faces. As a
15 | result, the mesh's faces will point to invalid vertices. This is because
16 | it's assumed that the collapse (pooling) operation will be accompanied by an
17 | unpooling operation, and that it won't be necessary to export a valid mesh
18 | created by this edge collapse. In other words, this edge collapse only
19 | updates the mesh with the information needed to pool edge-based features and
20 | determine which further edge collapses would be valid.
21 |
22 | Return True if the collapse succeeds. If the collapse would create
23 | non-manifold geometry, return False.
24 | """
25 |
26 | # Check for edge collapse validity.
27 | if not check_collapse_manifold(mesh, edge_key):
28 | return False
29 |
30 | # Collapse both sides (triangles).
31 | kept_vertex, masked_vertex = mesh.edges[edge_key]
32 | masked_edge_a = collapse_side(mesh, edge_key, 0, snapshot)
33 | masked_edge_b = collapse_side(mesh, edge_key, 2, snapshot)
34 |
35 | # Mask off the collapsed edge.
36 | mesh.edge_mask[edge_key] = False
37 |
38 | # Mask off the vertex that's being removed.
39 | mesh.vertex_mask[masked_vertex] = False
40 |
41 | # Move the kept vertex.
42 | mesh.vertices[kept_vertex, :] = (
43 | mesh.vertices[kept_vertex, :] + mesh.vertices[masked_vertex, :]
44 | ) * 0.5
45 |
46 | # This gets rid of references to the specified edge in vertex_to_edges.
47 | def discard_edge(discard_edge_key):
48 | vertex_a, vertex_b = mesh.edges[discard_edge_key]
49 | mesh.vertex_to_edges[vertex_a].remove(EdgeConnection(discard_edge_key, 0))
50 | mesh.vertex_to_edges[vertex_b].remove(EdgeConnection(discard_edge_key, 1))
51 |
52 | # Update the mesh's vertex_to_edges.
53 | masked_vertex_edges = mesh.vertex_to_edges[masked_vertex]
54 | discard_edge(edge_key)
55 | discard_edge(masked_edge_a)
56 | discard_edge(masked_edge_b)
57 | for edge_connection in masked_vertex_edges:
58 | mesh.edges[
59 | edge_connection.edge_index, edge_connection.index_in_edge
60 | ] = kept_vertex
61 | mesh.vertex_to_edges[kept_vertex].update(masked_vertex_edges)
62 | mesh.vertex_to_edges[masked_vertex] = None
63 |
64 | # Update the mesh's num_edges.
65 | mesh.num_edges -= 3
66 |
67 | return True
68 |
69 |
70 | def check_collapse_manifold(mesh: Mesh, edge_key: int):
71 | """Return False if collapsing the specified edge would cause
72 | non-manifold geometry to be formed. There are two conditions that under
73 | which non-manifold geometry is created:
74 | 1) A vertex that neighbors one of the edges has degree 3.
75 | 2) The edge's vertices do not share exactly 2 vertex neighbors.
76 | """
77 |
78 | # If the edge doesn't exist anymore, it can't be collapsed.
79 | if mesh.edge_mask[edge_key] == False:
80 | return False
81 |
82 | vertex_a, vertex_b = mesh.edges[edge_key]
83 |
84 | # Get the one-ring neighbors around the edge's vertices.
85 | def get_neighbor_vertices(vertex):
86 | neighbor_has_degree_3 = False
87 | neighbor_vertices = set()
88 | for edge_connection in mesh.vertex_to_edges[vertex]:
89 | other_vertex = mesh.edges[
90 | edge_connection.edge_index, 1 - edge_connection.index_in_edge
91 | ]
92 | if len(mesh.vertex_to_edges[other_vertex]) == 3:
93 | neighbor_has_degree_3 = True
94 | break
95 |
96 | neighbor_vertices.add(other_vertex)
97 | return neighbor_vertices, neighbor_has_degree_3
98 |
99 | # The mesh will remain manifold as long as there are exactly two shared
100 | # neighbors.
101 | vertex_a_neighbors, a_neighbor_invalid = get_neighbor_vertices(vertex_a)
102 | vertex_b_neighbors, b_neighbor_invalid = get_neighbor_vertices(vertex_b)
103 | return (
104 | len(vertex_a_neighbors & vertex_b_neighbors) == 2
105 | and not a_neighbor_invalid
106 | and not b_neighbor_invalid
107 | )
108 |
109 |
110 | def collapse_side(mesh: Mesh, edge_key: int, side: int, snapshot: CollapseSnapshot):
111 | """Collapse the triangle to the specified side of an edge that's being
112 | collapsed. Update edge_mask to exclude one of the edges that becomes
113 | overlapping as a result of the collapse. Calling this alone will leave
114 | the mesh in an inconsistent state.
115 |
116 | Returns the key for the edge that's masked off.
117 | """
118 |
119 | # The triangle that's being collapsed has three edges: the one that's
120 | # being collapsed, its even neighbor (side is 0 or 2) and its odd
121 | # neighbor (side is 1 or 3). After the collapse, the even neighbor
122 | # remains, while the odd neighbor is masked off.
123 | assert side == 0 or side == 2
124 | (
125 | even_neighbor_key,
126 | odd_neighbor_key,
127 | even_lookup,
128 | _,
129 | _,
130 | odd_lookup_opposite,
131 | _,
132 | odd_neighbor_neighbors,
133 | ) = get_face_neighborhood(mesh, edge_key, side)
134 |
135 | # Define functions to get the even and odd sides within a side group.
136 | get_even_side = lambda x: x - x % 2
137 | get_odd_side = lambda x: get_even_side(x) + 1
138 |
139 | # Rewrite the even neighbor's information to make the collapse happen.
140 | # First, replace the even neighbor's even neighbor (the one within the
141 | # collapsing triangle) with the odd neighbor's even neighbor (the one
142 | # outside the collapsing triangle).
143 | redirect_edges(
144 | mesh,
145 | even_neighbor_key,
146 | get_even_side(even_lookup),
147 | odd_neighbor_neighbors[0],
148 | mesh.edge_lookup[odd_neighbor_key, odd_lookup_opposite],
149 | )
150 |
151 | # Next, do the same for the even neighbor's odd neighbor.
152 | redirect_edges(
153 | mesh,
154 | even_neighbor_key,
155 | get_odd_side(even_lookup),
156 | odd_neighbor_neighbors[1],
157 | mesh.edge_lookup[odd_neighbor_key, odd_lookup_opposite + 1],
158 | )
159 |
160 | # Reparent the collapsed edge and the odd neighbor to the even neighbor.
161 | snapshot.reparent(odd_neighbor_key, even_neighbor_key)
162 | snapshot.reparent(edge_key, even_neighbor_key)
163 |
164 | # Mask off the odd neighbor.
165 | mesh.edge_mask[odd_neighbor_key] = False
166 | return odd_neighbor_key
167 |
168 |
169 | def redirect_edges(
170 | mesh: Mesh,
171 | edge_key_a: int,
172 | b_side_for_a: int,
173 | edge_key_b: int,
174 | a_side_for_b: int,
175 | ):
176 | """Update edge_to_neighbors and edge_lookup so that edges A and B point
177 | towards each other. Calling this alone will leave the mesh in an
178 | inconsistent state.
179 | """
180 | mesh.edge_to_neighbors[edge_key_a, b_side_for_a] = edge_key_b
181 | mesh.edge_to_neighbors[edge_key_b, a_side_for_b] = edge_key_a
182 | mesh.edge_lookup[edge_key_a, b_side_for_a] = a_side_for_b
183 | mesh.edge_lookup[edge_key_b, a_side_for_b] = b_side_for_a
184 |
185 |
186 | def get_face_neighborhood(mesh: Mesh, edge_key: int, side: int):
187 | """Gather information needed for edge collapse. In a watertight mesh,
188 | each triangular face has three adjacent faces.
189 | """
190 | assert side == 0 or side == 2
191 |
192 | # Find the keys for the neighbors on the specified sides.
193 | # Together, the original edge and its neighbors form a triangle.
194 | even_neighbor_key = mesh.edge_to_neighbors[edge_key, side]
195 | odd_neighbor_key = mesh.edge_to_neighbors[edge_key, side + 1]
196 |
197 | # Use edge_lookup to determine what side the original edge is on from
198 | # its neighbors' perspectives.
199 | even_lookup = mesh.edge_lookup[edge_key, side]
200 | odd_lookup = mesh.edge_lookup[edge_key, side + 1]
201 |
202 | # Define a lambda that gets the side index for the even neighbor that's
203 | # opposite an edge. Recall that an edge's 4 neighbors have sides 0
204 | # through 3, where (0, 1) and (2, 3) are on opposite sides of the edge.
205 | get_other_side = lambda x: 0 if x >= 2 else 2
206 | even_lookup_opposite = get_other_side(even_lookup)
207 | odd_lookup_opposite = get_other_side(odd_lookup)
208 |
209 | # For the original edge's neighbors on the specified side, get the other
210 | # two neighbors.
211 | even_neighbor_neighbors = (
212 | mesh.edge_to_neighbors[even_neighbor_key, even_lookup_opposite],
213 | mesh.edge_to_neighbors[even_neighbor_key, even_lookup_opposite + 1],
214 | )
215 | odd_neighbor_neighbors = (
216 | mesh.edge_to_neighbors[odd_neighbor_key, odd_lookup_opposite],
217 | mesh.edge_to_neighbors[odd_neighbor_key, odd_lookup_opposite + 1],
218 | )
219 |
220 | return (
221 | even_neighbor_key,
222 | odd_neighbor_key,
223 | even_lookup,
224 | odd_lookup,
225 | even_lookup_opposite,
226 | odd_lookup_opposite,
227 | even_neighbor_neighbors,
228 | odd_neighbor_neighbors,
229 | )
--------------------------------------------------------------------------------
/source/loss/ChamferLossLayer.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 | import tensorflow_graphics.nn.loss.chamfer_distance as chamfer_distance
3 | from tensorflow.keras.layers import Layer
4 | import tensorflow_probability as tfp
5 |
6 |
7 | class ChamferLossLayer(Layer):
8 | def __init__(self) -> None:
9 | super(ChamferLossLayer, self).__init__()
10 |
11 | def call(self, cloud1, cloud2, num_samples):
12 | """
13 | Arguments:
14 | cloud1 is a tf tensor of shape (N,P1,D) where:
15 | N is number of clouds in batch,
16 | P1 is the max number of points amoung all of the images in the batch
17 | D is dimensionality of system
18 | cloud2 is a tf tensor of shape (M,P2,D) where:
19 | N is number of clouds in batch,
20 | P2 is the max number of points amoung all of the images in the batch
21 | D is dimensionality of system
22 | """
23 |
24 | # Assert that cloud1 and cloud2 are tensors of the correct shape.
25 | assert tf.is_tensor(cloud1)
26 | assert tf.shape(cloud1)[-1] == 3
27 | assert tf.is_tensor(cloud2)
28 | assert tf.shape(cloud2)[-1] == 3
29 |
30 | def subsample(cloud):
31 | num_points = tf.shape(cloud)[-2]
32 | point_sample_probs = tf.ones(num_points) / tf.cast(
33 | num_points, dtype=tf.float32
34 | )
35 | point_distribution = tfp.distributions.Categorical(probs=point_sample_probs)
36 | points_to_sample = point_distribution.sample(num_samples)
37 | return tf.gather(cloud, points_to_sample)
38 |
39 | subsampled_cloud1 = subsample(cloud1)
40 | subsampled_cloud2 = subsample(cloud2)
41 |
42 | # Compute bidirectional, average loss using built in tfg function.
43 | # Returns the sum of (the mean, minimum, squared distance from cloud 1
44 | # to 2) and vice-versa (the mean, minimum, squared distance from cloud 2
45 | # to 1).
46 | return chamfer_distance.evaluate(subsampled_cloud1, subsampled_cloud2)
--------------------------------------------------------------------------------
/source/loss/ConvergenceDetector.py:
--------------------------------------------------------------------------------
1 | from collections import deque
2 | from typing import Deque
3 |
4 |
5 | class ConvergenceDetector:
6 | window_size: int
7 | threshold: float
8 | loss_window: Deque[float]
9 | running_average_window: Deque[float]
10 |
11 | def __init__(self, window_size: int = 25, threshold: float = 0.99) -> None:
12 | super().__init__()
13 | self.window_size = window_size
14 | self.threshold = threshold
15 | self.loss_window = deque()
16 | self.running_average_window = deque()
17 |
18 | def step(self, loss_value: float) -> bool:
19 | # Update the loss window.
20 | self.loss_window.append(loss_value)
21 | if len(self.loss_window) > self.window_size:
22 | self.loss_window.popleft()
23 |
24 | # Calculate the average over the window.
25 | loss_sum = 0
26 | for loss in self.loss_window:
27 | loss_sum += loss
28 | loss_average = loss_sum / len(self.loss_window)
29 |
30 | # Update the running average window.
31 | self.running_average_window.append(loss_average)
32 | if len(self.running_average_window) > self.window_size:
33 | past_average = self.running_average_window.popleft()
34 |
35 | # Indicate that convergence has been detected if the running average
36 | # of the loss hasn't been decreasing.
37 | if loss_average > past_average * self.threshold:
38 | return True
39 |
40 | return False
--------------------------------------------------------------------------------
/source/loss/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/loss/__init__.py
--------------------------------------------------------------------------------
/source/loss/__tests__/TestChamferLoss.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from tensorflow.python.keras.regularizers import get
3 | import trimesh
4 | import numpy as np
5 | from typing import Tuple
6 | import tensorflow as tf
7 | from ..ChamferLossLayer import ChamferLossLayer
8 |
9 |
10 | class TestChamferLoss(unittest.TestCase):
11 | def load_obj_into_mesh(self, file_name: str) -> Tuple[np.ndarray]:
12 | with open(file_name, "r") as f:
13 | mesh = trimesh.exchange.obj.load_obj(f)
14 | return np.float32(mesh["vertices"]), mesh["faces"]
15 |
16 | def test_chamfer_loss_test(self):
17 | chamfer_loss_layer = ChamferLossLayer()
18 | cloud1 = tf.convert_to_tensor([[1.0, 1.0, 1.0]], dtype=tf.float32)
19 | cloud2 = tf.convert_to_tensor(
20 | [[0.0, 0.0, 0.0], [2.0, 1.0, 1.0], [2.0, 3.0, 4.0]],
21 | dtype=tf.float32,
22 | )
23 | # This is a bidirectional average square loss.
24 | # It should be (1)^2 + (3+1^2+(1+2^2+3^2))/3 = 1 + 6 = 7
25 | loss = chamfer_loss_layer(cloud1, cloud2)
26 | self.assertTrue(loss.numpy() - 7.0 < 0.01)
27 |
--------------------------------------------------------------------------------
/source/loss/__tests__/TestLoss.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from tensorflow.python.keras.regularizers import get
3 | import trimesh
4 | import numpy as np
5 | from typing import Tuple
6 | import tensorflow as tf
7 |
8 | from ..loss import (
9 | BeamGapLossLayer,
10 | discrete_project,
11 | get_looping_points,
12 | distance_within_cone,
13 | )
14 | from ...mesh.Mesh import Mesh
15 |
16 |
17 | class TestLoss(unittest.TestCase):
18 | def load_obj_into_mesh(self, file_name: str) -> Tuple[np.ndarray]:
19 | with open(file_name, "r") as f:
20 | mesh = trimesh.exchange.obj.load_obj(f)
21 | return np.float32(mesh["vertices"]), mesh["faces"]
22 |
23 | def test_initialize_beam_gap_loss_layer(self):
24 | init_test = BeamGapLossLayer("cpu", discrete_project)
25 | self.assertTrue(True)
26 |
27 | def test_update_point_masks(self):
28 | def testing_target_function(mesh, point_cloud, threshold):
29 | return (
30 | tf.convert_to_tensor([[0.0, 0.0, 0.0], [1.0, 0.0, 2.0]]),
31 | tf.convert_to_tensor([True, False]),
32 | )
33 |
34 | test_layer = BeamGapLossLayer("cpu", testing_target_function)
35 |
36 | mesh = []
37 | target_point_cloud = []
38 | test_layer.update_points_masks(mesh, target_point_cloud)
39 | self.assertTrue(
40 | np.prod(
41 | np.equal(test_layer.points.numpy(), [[0.0, 0.0, 0.0], [1.0, 0.0, 2.0]])
42 | )
43 | )
44 | self.assertTrue(np.prod(np.equal(test_layer.mask.numpy(), [True, False])))
45 |
46 | def test_looping_mask(self):
47 | cloud1 = np.array(
48 | [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 1.0], [0.0, 0.0, 10.0]]
49 | )
50 | cloud2 = np.array([[1.0, 0.0, 0.0], [2.0, 0.0, 0.0], [1.0, 1.0, 1.5]])
51 | cloud1_loop = get_looping_points(cloud1, cloud2)
52 | cloud2_loop = get_looping_points(cloud2, cloud1)
53 | self.assertTrue(np.prod(np.equal(cloud1_loop, [True, True, True, False])))
54 | self.assertTrue(np.prod(np.equal(cloud2_loop, [True, True, True])))
55 |
56 | def test_distance_within_cone(self):
57 | cloud1 = np.array([[0.0, 0.0, 0.0], [0.0, 5.0, 5.0], [1.0, 1.0, 1.0]])
58 | cloud1_normals = np.array(
59 | [[1.0, 0.0, 0.0], [0.0, -0.7071, -0.7071], [1.0, 0.0, 0.0]]
60 | )
61 | cloud2 = np.array([[1.0, 0.0, 0.0], [2.0, 0.0, 0.0]])
62 | dist, mask = distance_within_cone(cloud1, cloud1_normals, cloud2, 0.99)
63 | test = np.sum(dist - np.array([[1.0, 2.0], [7.14, 7.35], [1.41, 1.73]]))
64 | self.assertTrue(
65 | np.sum(dist - np.array([[1.0, 2.0], [7.14, 7.35], [1.41, 1.73]])) < 0.1
66 | )
67 | self.assertTrue(
68 | np.prod(
69 | np.equal(mask, np.array([[True, True], [True, False], [False, False]]))
70 | )
71 | )
72 |
73 | def test_discrete_project(self):
74 | vertices, faces = self.load_obj_into_mesh("data/objs/icosahedron.obj")
75 | mesh = Mesh(vertices, faces)
76 | test_layer = BeamGapLossLayer("cpu", discrete_project)
77 | test_point_cloud, _ = mesh.sample_surface(
78 | tf.convert_to_tensor(mesh.vertices), 3
79 | )
80 | test_layer.update_points_masks(mesh, test_point_cloud)
81 | points = test_layer.points.numpy()
82 | mask = test_layer.mask.numpy()
83 | self.assertTrue(True)
84 |
--------------------------------------------------------------------------------
/source/loss/__tests__/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/loss/__tests__/__init__.py
--------------------------------------------------------------------------------
/source/loss/loss.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import tensorflow as tf
3 | import scipy
4 | from tensorflow.keras.layers import Layer
5 | from ..mesh.Mesh import Mesh
6 |
7 |
8 | class BeamGapLossLayer(Layer):
9 | def __init__(self, target_function) -> None:
10 |
11 | super(BeamGapLossLayer, self).__init__()
12 |
13 | # Sets the target function. In this case is mesh.discrete project except for debugging
14 | self.target_function = target_function
15 |
16 | # Initializes:
17 | # self.points which is used to store the points that will eventually be used to calculate beam gap loss
18 | # self.masks which is used to store the mask that will eventually be used to calculate beam gap loss
19 | self.points = None
20 | self.masks = None
21 |
22 | def update_points_masks(self, mesh, vertices, target_point_cloud):
23 |
24 | """
25 | Updates self.points and self.mask
26 | self.points: a (num_faces, 3) tensor that repersents the XYZ coordinates for the nearest point_cloud point to
27 | a given face's midpoints. Instead coordinates will be nan if:
28 | the face midpoint is looping (see below)
29 | there is no point cloud point within the cone (see below)
30 | self.mask: a (num_faces) tensor that is True if the coordinates in a given row are not nan, alse otherwise
31 | """
32 | self.points, self.mask = self.target_function(
33 | mesh, vertices, target_point_cloud, 0.99
34 | )
35 |
36 | def call(self, mesh: Mesh, vertices: tf.Tensor):
37 | # Only calculate beam-gap loss for points that aren't masked off.
38 | affected_faces = mesh.faces[self.mask]
39 | affected_points = self.points[self.mask]
40 |
41 | # This loss is the average distance from each unmasked point to the
42 | # closest valid point (i.e. within the cone) in the point cloud.
43 | face_vertices = tf.gather(vertices, affected_faces)
44 | face_midpoints = tf.math.reduce_mean(face_vertices, axis=1)
45 | distances = tf.norm(affected_points - face_midpoints, axis=1)
46 | return 10.0 * tf.math.reduce_mean(distances)
47 |
48 |
49 | def discrete_project(mesh, point_cloud, vertices=None, threshold=0.9):
50 | """
51 | ARGS:
52 | self: a Mesh Object
53 | vertices: a tf tensor of shape (num_vertices, 3)
54 | point_cloud: a tf tensor of shap (num_points_in_cloud, 3)
55 | threshold: a float representing the cosine of the cone angle when checking to see if there exist any points
56 | within the target pointcloud that are relevant for the mash
57 |
58 | returns:
59 | pc_per_face: a (num_faces, 3) tensor that repersents the XYZ coordinates for the nearest point_cloud point to
60 | a given face's midpoints. Instead coordinates will be nan if:
61 | the face midpoint is looping (see below)
62 | there is no point cloud point within the cone (see below)
63 | pc_is_not_nan: a (num_faces) tensor that is True if the coordinates in a given row are not nan, alse otherwise
64 | """
65 |
66 | # Check that point_cloud is tensor with corrrect dimension size
67 | # Check that self is a Mesh
68 | assert tf.is_tensor(point_cloud)
69 | if not tf.is_tensor(vertices):
70 | vertices = mesh.vertices
71 | else:
72 | vertices = vertices.numpy()
73 |
74 | assert tf.shape(point_cloud)[-1] == 3
75 | assert isinstance(mesh, Mesh)
76 |
77 | # Cast to data type double
78 | # Also get numpy to stop gradient
79 | point_cloud = tf.cast(point_cloud, dtype=tf.double) # (num_points_in_cloud, 3)
80 | point_cloud = point_cloud.numpy()
81 |
82 | # Get face normals for mesh, point normal to the mesh out of the face midpoints
83 | mesh_normals, _ = mesh.generate_face_areas_normals(
84 | tf.convert_to_tensor(vertices)
85 | ) # (num_faces, 3)
86 |
87 | mesh_normals = mesh_normals.numpy()
88 |
89 | # Get set of 3 vertexes associated with each face.
90 | # Compute the midpoint of every face by taking the mean of each dimension over the 3 vertices
91 | # Also get numpy as to stop gradient
92 | mid_points = vertices[
93 | mesh.faces
94 | ] # (num_faces, 3(3 vertexes per face), 3(3 coordinates per vertex))
95 | mid_points = tf.math.reduce_mean(
96 | mid_points, axis=1
97 | ).numpy() # (num_faces, 3(3 avg coordiates per face))
98 |
99 | # See function below.
100 | # looping_mesh_points_mask: numpy boolean array of shape (num_faces) that is True if any of the k nearest points cloud
101 | # points of the row index's mesh point has the original mesh point amoung its k nearest neighbors
102 | looping_mesh_points_mask = get_looping_points(mid_points, point_cloud)
103 |
104 | # We are only interested in points that DONT map back to themselves because this indicates that the mesh is not
105 | # entering a deep enough cavity. Thus we pull from mid_points and normals using ~ mask
106 | # From here on, we will call the number of non-looping points num_non_looping
107 | masked_mid_points = mid_points[~looping_mesh_points_mask, :] # (num_non_looping, 3)
108 | masked_normals = mesh_normals[~looping_mesh_points_mask, :] # (num_non_looping, 3)
109 |
110 | # See function below.
111 | # distance: numpy arrar of shape (num_non_looping, num_points) which is the l2 distance from each of the face points to each of the
112 | # point cloud points
113 | # masked_within_cone: numpy boolean array of shape (num_non_looping, num_points) that is True if the (unit displacement between
114 | # the face mid_point and the point cloud point) dotted with (the face unit normal) is absolute value greater than the
115 | # threshold
116 | distance, masked_within_cone = distance_within_cone(
117 | masked_mid_points, masked_normals, point_cloud, threshold
118 | )
119 |
120 | # For every non looping point that doesn't have a corresponding point on the PC within cone
121 | # Set the distances between that mesh point and all the PC points = inf
122 | distance[~masked_within_cone] += float(
123 | "inf"
124 | ) # (num_non_looping, num_points_in_cloud)
125 |
126 | # compute the minumum for each point in num_non_looping of the distances to all the points in the point cloud
127 | # also compute the index of the point cloud point associated with the minimum distance
128 | min = np.amin(distance, axis=-1) # (num_non_looping)
129 | arg_min = np.argmin(distance, axis=-1) # (num_non_looping)
130 |
131 | # For each non looping point gets the XYZ coordniates of the closest point in the point cloud
132 | # Fot all points where the was no point in the point cloud within the cone, sets the XYZ coordinates to nan
133 | pc_per_masked_face = point_cloud[arg_min, :].copy()
134 | pc_per_masked_face[min == float("inf"), :] = float("nan")
135 |
136 | # creates one of the output objects as a tensor with shape (num_faces, 3)
137 | pc_per_face = np.zeros(
138 | shape=(np.shape(mid_points)[0], 3), dtype=pc_per_masked_face.dtype
139 | )
140 |
141 | # for every non looping point, we have already computed the xyz point on the PC that is closest
142 | # so this just writes those values to be the percomputed values
143 | pc_per_face[~looping_mesh_points_mask, :] = pc_per_masked_face
144 |
145 | # for all the looping points, just set the closest poinct cloud XYZs to nan
146 | pc_per_face[looping_mesh_points_mask, :] = float("nan")
147 |
148 | # checks to see if the XYZ points are nan becasue nan=/=nan
149 | # True indicates the XYZ point is real which happens when the corresponing mesh midpoint is non-looping AND
150 | # there is a point on the point cloud within the cone
151 | pc_is_non_nan = pc_per_face[:, 0] == pc_per_face[:, 0]
152 |
153 | # return objects
154 | return tf.convert_to_tensor(pc_per_face, dtype=tf.float32), tf.convert_to_tensor(
155 | pc_is_non_nan, dtype=tf.bool
156 | )
157 |
158 |
159 | def get_looping_points(mid_points, point_cloud, k=3):
160 | """
161 | ARGS:
162 | mid_points: numpy array of shape (num_faces, 3) representing the XYZ locations of the center of a mesh's faces
163 | point_cloud: numpy array of shape (num_points_in_cloud, 3) representing the XYZ locations of the points in the cloud
164 |
165 | return:
166 | looping_mesh_points_mask: numpy boolean array of shape (num_faces) that is True if any of the k nearest points cloud
167 | points of the row index's mesh point has the original mesh point amoung its k nearest neighbors
168 | """
169 |
170 | # create a scipy KDTree to represent points
171 | # ref: https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.KDTree.html
172 | KDTree_cloud = scipy.spatial.KDTree(point_cloud)
173 | KDTree_mesh = scipy.spatial.KDTree(mid_points)
174 |
175 | # querries each KDTree to find the k nearest neighbors. As an example:
176 | # k_closest_cloud_points2mesh will have the shape (np.shape(mid_points)[0], k (in our case 3))
177 | # each row corresponds to a midpoint, the nth index in that row corresponds to the index row into point_cloud
178 | # that is nth closest to the given midpoint
179 | # The same is querried for the mesh cloud as well (can be thought of as bi directional)
180 | _, k_closest_cloud_points2mesh = KDTree_cloud.query(
181 | mid_points, k=k, eps=0
182 | ) # (num_faces, k)
183 | _, k_closest_mesh_points2cloud = KDTree_mesh.query(
184 | point_cloud, k=k, eps=0
185 | ) # (num_points_in_cloud, k)
186 |
187 | # gets, for every face mid_point in the mesh, the k nearest neighbors in the point cloud
188 | # then, for all k of those points in the point cloud, gets the k nearest points in the set of mid_points
189 | # finally, reshapes from (num_faces, 3, 3) to (num_faces, 9)
190 |
191 | # as an example if the meshes were identical (mid_points == point_cloud)
192 | # with points [0,1,2] in mid_points and [a,b,c] in point_cloud
193 | # where dist(0,1) 0:
213 | # True if a point maps back to itself at least once amoung 9 options
214 | # boolean array of shape (num_faces)
215 |
216 | looping_mesh_points_mask = (
217 | np.sum(
218 | np.equal(
219 | neighbors_neighbors,
220 | np.expand_dims(np.arange(0, np.shape(mid_points)[0]), axis=1),
221 | ),
222 | axis=1,
223 | )
224 | > 0
225 | )
226 |
227 | return looping_mesh_points_mask
228 |
229 |
230 | def distance_within_cone(mid_points, normals, point_cloud, threshold):
231 | """
232 | ARGS:
233 | mid_points: numpy array of shape (num_faces, 3) representing the XYZ locations of the center of a mesh's faces
234 | normals: numpy array of shape (num_faces, 3) representing the normal vector to the face
235 | point_cloud: numpy array of shape (num_points_in_cloud, 3) representing the XYZ locations of the points in the cloud
236 | threshold: a float representing the cosine of the cone angle when checking to see if there exist any points
237 | within the target pointcloud that are relevant for the mash
238 |
239 | return:
240 | distance: numpy arrar of shape (num_faces, num_points) which is the l2 distance from each of the face points to each of the
241 | point cloud points
242 | masked_within_cone: numpy boolean array of shape (num_faces, num_points) that is True if the (unit displacement between
243 | the face mid_point and the point cloud point) dotted with (the face unit normal) is absolute value greater than the
244 | threshold
245 | """
246 |
247 | # This uses broadcasting to compute the displacement from every point in the mid_points
248 | # to every point in the point cloud
249 | displacement = (
250 | mid_points[:, None, :] - point_cloud
251 | ) # (num_mid_points, num_points_in_cloud, 3)
252 |
253 | # Compute distance via L2 norm on displacements
254 | distance = np.linalg.norm(
255 | displacement, axis=-1
256 | ) # (num_mid_points, num_points_in_cloud)
257 |
258 | # Another big, multistep operation.
259 | # First, normalize the displacement by its distance to compute the unit normal in the direction of the displacement
260 | # Second, compute the dot product of this norm with the mesh surface norm:
261 | # If the dot product is close to 1 then these vectors are nearly parallel
262 | # If the dot product is close to 0 then these vectors are perpendicular
263 | # Third, create the mask by checking if the dot product is close to 1 (within threshold)
264 | # In the end, boolean array of shape (num_non_looping)
265 | # True if there is a point within the cone
266 |
267 | masked_within_cone = np.array(
268 | np.abs(
269 | np.sum(
270 | (displacement / distance[:, :, None]) * normals[:, None, :],
271 | axis=-1,
272 | )
273 | )
274 | > threshold
275 | )
276 |
277 | return distance, masked_within_cone
--------------------------------------------------------------------------------
/source/mesh/EdgeConnection.py:
--------------------------------------------------------------------------------
1 | class EdgeConnection:
2 | """This stores information about how a vertex is connected to an edge."""
3 |
4 | # The index of the edge the vertex is connected to.
5 | edge_index: int
6 |
7 | # The index of the vertex within the edge (i.e. 0 or 1).
8 | index_in_edge: int
9 |
10 | def __init__(self, edge_index: int, index_in_edge: int) -> None:
11 | assert edge_index >= 0
12 | assert index_in_edge == 0 or index_in_edge == 1
13 | self.edge_index = edge_index
14 | self.index_in_edge = index_in_edge
15 |
16 | def __hash__(self) -> int:
17 | return (self.edge_index, self.index_in_edge).__hash__()
18 |
19 | def __eq__(self, o: object) -> bool:
20 | if not isinstance(o, EdgeConnection):
21 | return False
22 | return self.edge_index == o.edge_index and self.index_in_edge == o.index_in_edge
--------------------------------------------------------------------------------
/source/mesh/Mesh.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import tensorflow as tf
3 | import tensorflow_probability as tfp
4 | from .EdgeConnection import EdgeConnection
5 | from typing import Optional, List, Set
6 |
7 |
8 | class Mesh:
9 | # Hold the mesh's vertices as xyz points.
10 | vertices: np.ndarray # (num_vertices, 3)
11 |
12 | # Hold the mesh's faces as triplets of vertex indices.
13 | faces: np.ndarray # (num_faces, 3)
14 |
15 | # Hold the mesh's edges as couples of vertex indices.
16 | # Note that an edge's key is its row index within this array.
17 | edges: np.ndarray # (num_edges, 2)
18 |
19 | # Map each edge's key to those of its four neighbors.
20 | edge_to_neighbors: np.ndarray # (num_edges, 4)
21 |
22 | # Maps each edge's key to that edge's index within other entries in edge_to_neighbors.
23 | # Let k be an edge's key. Let k'_n be the nth value of edge_to_neighbors[k].
24 | # Then the nth element of edge_lookup[k] will be the index of k within edge_to_neighbors[k'_n].
25 | # This is used for MeshCNN's pooling operation.
26 | edge_lookup: np.ndarray # (num_edges, 4)
27 |
28 | # Map vertex indices to lists of edge indices.
29 | vertex_to_edges: List[Optional[Set[EdgeConnection]]]
30 |
31 | # This indicates whether an edge is still part of the mesh.
32 | edge_mask: np.ndarray
33 |
34 | # This indicates whether a vertex is still part of the mesh.
35 | vertex_mask: np.ndarray
36 |
37 | # The number of non-masked edges in the mesh.
38 | num_edges: int
39 |
40 | # The maximum vertex degree of the initial (un-collapsed) mesh.
41 | max_vertex_degree: int
42 |
43 | # This contains the contents of vertex_to_edges. The shape is the follwoing:
44 | # (num_vertices * max_vertex_degree, 2)
45 | # The first column contains the edge index, while the second contains the
46 | # index in the edge. For vertices whose degree is less than the maximum
47 | # degree, the empty parts have index -1. This is used to convert edge
48 | # features to vertex features.
49 | vertex_to_edges_tensor: tf.Tensor
50 |
51 | # This is also used to convert edge features to vertex features.
52 | vertex_to_degree: tf.Tensor
53 |
54 | def __init__(self, vertices: np.ndarray, faces: np.ndarray) -> None:
55 | """Create a new mesh.
56 |
57 | vertices: the mesh's vertices with shape (num_vertices, 3).
58 | faces: the mesh's faces with shape (num_faces, 3).
59 | """
60 | assert isinstance(vertices, np.ndarray)
61 | assert vertices.shape[1] == 3
62 | assert isinstance(faces, np.ndarray)
63 | assert faces.shape[1] == 3
64 | self.vertices = vertices
65 | self.faces = faces
66 | self.build_acceleration_structures()
67 | self.edge_mask = np.ones((self.edges.shape[0]), dtype=np.bool)
68 | self.vertex_mask = np.ones((self.vertices.shape[0]), dtype=np.bool)
69 | self.num_edges = self.edges.shape[0]
70 |
71 | def build_acceleration_structures(self):
72 | # Map vertex indices
73 | self.vertex_to_edges = [set() for _ in range(self.vertices.shape[0])]
74 |
75 | # Each edge has a unique key.
76 | # These map between (smaller vertex index, larger vertex index) and an edge's unique key.
77 | edge_to_key = {}
78 | key_to_edge = []
79 |
80 | # This maps each edge's key to its neighboring edges' keys.
81 | # In a watertight mesh, each edge has four neighbors.
82 | # The neighbors are the four other edges of the two triangles an edge is connected to.
83 | edge_to_neighbors: List[List[int]] = []
84 |
85 | # This maps each edge to the number of neighbors that have been encountered.
86 | edge_to_seen_neighbors: List[int] = []
87 |
88 | # This maps each edge's key to that edge's index within other entries in edge_to_neighbors.
89 | # Let k be an edge's key. Let k'_n be the nth value of edge_to_neighbors[k].
90 | # Then the nth element of edge_lookup[k] will be the index of k within edge_to_neighbors[k'_n].
91 | edge_lookup: List[List[int]] = []
92 |
93 | for face in self.faces:
94 | # Create a list of the face's edges.
95 | # Each entry is a sorted list of the edge's two vertex indices.
96 | face_edges = [tuple(sorted([face[i], face[(i + 1) % 3]])) for i in range(3)]
97 |
98 | # The stuff here happens once per edge (the first time it's encountered).
99 | for edge_vertices in face_edges:
100 | if edge_vertices not in edge_to_key:
101 | # The edge gets a unique key (its index).
102 | edge_key = len(edge_to_key)
103 | edge_to_key[edge_vertices] = edge_key
104 | key_to_edge.append(edge_vertices)
105 |
106 | # Set up data structures for the new edge.
107 | edge_to_neighbors.append([None, None, None, None])
108 | edge_lookup.append([None, None, None, None])
109 | edge_to_seen_neighbors.append(0)
110 |
111 | # Associate each vertex with the edge.
112 | v0, v1 = edge_vertices
113 | self.vertex_to_edges[v0].add(EdgeConnection(edge_key, 0))
114 | self.vertex_to_edges[v1].add(EdgeConnection(edge_key, 1))
115 |
116 | # Associate edges with their neighbors.
117 | # This happens in a separate loop because it requires all encountered edges to have keys.
118 | for edge_index, edge_vertices in enumerate(face_edges):
119 | # Get the edge's neighbors' keys.
120 | neighbor_0_key = edge_to_key[face_edges[(edge_index + 1) % 3]]
121 | neighbor_1_key = edge_to_key[face_edges[(edge_index + 2) % 3]]
122 |
123 | # Update edge_to_neighbors.
124 | edge_key = edge_to_key[edge_vertices]
125 | seen_neighbors = edge_to_seen_neighbors[edge_key]
126 | edge_to_neighbors[edge_key][seen_neighbors + 0] = neighbor_0_key
127 | edge_to_neighbors[edge_key][seen_neighbors + 1] = neighbor_1_key
128 | edge_to_seen_neighbors[edge_key] += 2
129 |
130 | # Create the edge lookup.
131 | # This happens in a separate loop because it requires all encountered edges' seen neighbor counts to be up to date.
132 | for edge_index, edge_vertices in enumerate(face_edges):
133 | # Get the edge's neighbors' keys.
134 | neighbor_0_key = edge_to_key[face_edges[(edge_index + 1) % 3]]
135 | neighbor_1_key = edge_to_key[face_edges[(edge_index + 2) % 3]]
136 |
137 | # Find how many neighbors the neighbors have seen.
138 | neighbor_0_seen_neighbors = edge_to_seen_neighbors[neighbor_0_key]
139 | neighbor_1_seen_neighbors = edge_to_seen_neighbors[neighbor_1_key]
140 |
141 | # Deduce the current edge's index in its neighbors' entries in edge_to_neighbors.
142 | # Note: The keys to edge_lookup here are the same as the keys to edge_to_neighbors in the previous loop.
143 | # They just look different because seen_neighbors was incremented by 2.
144 | edge_key = edge_to_key[edge_vertices]
145 | seen_neighbors = edge_to_seen_neighbors[edge_key]
146 | edge_lookup[edge_key][seen_neighbors - 2] = (
147 | neighbor_0_seen_neighbors - 1
148 | )
149 | edge_lookup[edge_key][seen_neighbors - 1] = (
150 | neighbor_1_seen_neighbors - 2
151 | )
152 |
153 | # Save the results to instance variables.
154 | self.edges = np.array(key_to_edge, dtype=np.int32)
155 | self.edge_to_neighbors = np.array(edge_to_neighbors, dtype=np.int64)
156 | self.edge_lookup = np.array(edge_lookup, dtype=np.int64)
157 |
158 | # Record the maximum vertex degree.
159 | self.max_vertex_degree = max([len(ec) for ec in self.vertex_to_edges])
160 |
161 | # This is used to extract vertex features from edges.
162 | self.vertex_to_edges_tensor = []
163 | self.vertex_to_degree = []
164 | for ecs in self.vertex_to_edges:
165 | vertex_info = [(ec.edge_index, ec.index_in_edge) for ec in ecs]
166 |
167 | # For vertices whose degree is less than the maximum vertex degree
168 | # in the mesh, we specify (num_edges, 0) as the index of the edge.
169 | # This is because the edge features are assumed to be padded with
170 | # an extra zero feature at the end that can be indexed this way.
171 | # This is necessary for the CPU implementation to work because
172 | # tf.gather_nd will give an error when an invalid index is given on
173 | # CPU (instead of just returning a zero feature like on GPU).
174 | num_e = self.edges.shape[0]
175 | vertex_info.extend(
176 | [(num_e, 0) for _ in range(self.max_vertex_degree - len(ecs))]
177 | )
178 | self.vertex_to_edges_tensor.extend(vertex_info)
179 | self.vertex_to_degree.append(len(ecs))
180 | self.vertex_to_edges_tensor = tf.convert_to_tensor(self.vertex_to_edges_tensor)
181 | self.vertex_to_degree = tf.convert_to_tensor(
182 | self.vertex_to_degree, dtype=tf.float32
183 | )
184 |
185 | def collapse_masked_elements(self) -> None:
186 | """Rebuild the mesh without its masked elements. This creates a smaller
187 | mesh where nothing is masked off.
188 | """
189 |
190 | # Map each unmasked vertex to its new index.
191 | self.vertices = self.vertices[self.vertex_mask]
192 | new_vertex_indices = np.zeros_like(self.vertex_mask, dtype=np.int32)
193 | new_vertex_indices[self.vertex_mask] = np.arange(self.vertices.shape[0])
194 |
195 | # Update the edges. This requires two changes:
196 | # 1) Masked-off edges have to be removed.
197 | # 2) The vertex pair defining an edge has to be re-indexed to account
198 | # for masked-off vertices that were removed.
199 | self.edges = new_vertex_indices[self.edges[self.edge_mask]]
200 |
201 | # Map each unmasked edge to its new index.
202 | new_edge_indices = np.zeros_like(self.edge_mask, dtype=np.int32)
203 | new_edge_indices[self.edge_mask] = np.arange(self.edges.shape[0])
204 |
205 | # Update edge_to_neighbors. This similarly requires two changes:
206 | # 1) Masked-off edges have to be removed.
207 | # 2) The neighbors have to be re-indexed to account for masked-off edges
208 | # that were removed.
209 | self.edge_to_neighbors = new_edge_indices[
210 | self.edge_to_neighbors[self.edge_mask]
211 | ]
212 |
213 | # Update vertex_to_edges.
214 | new_vertex_to_edges = []
215 | for edge_connections in self.vertex_to_edges:
216 | # Remove masked-off vertices.
217 | if edge_connections is None:
218 | continue
219 |
220 | # Re-index edge keys (indices).
221 | new_edge_connections = {
222 | EdgeConnection(new_edge_indices[old.edge_index], old.index_in_edge)
223 | for old in edge_connections
224 | }
225 | new_vertex_to_edges.append(new_edge_connections)
226 | self.vertex_to_edges = new_vertex_to_edges
227 |
228 | # Update edge_lookup. This simply requires masked edges to be removed.
229 | self.edge_lookup = self.edge_lookup[self.edge_mask]
230 |
231 | # Reset the masks.
232 | self.edge_mask = np.ones((self.edges.shape[0],), dtype=np.bool)
233 | self.vertex_mask = np.ones((self.vertices.shape[0],), dtype=np.bool)
234 |
235 | def generate_face_areas_normals(self, vertices):
236 | """
237 | ARGS:
238 | vertices: a tensor of shape (num_verticies, 3) numpy array holding the XYZ coordinates of each vertex
239 | self.faces: a (num_faces, 3) numpy array containing the row index for all three verticies
240 | that make up a face
241 | return:
242 | face_unit_normals: a (num_faces, 3) numpy array represeting the XYZ components of the face's normal vector
243 | face_areas: a (num_faces) numpy array representing the area of the face
244 | """
245 | faces = self.faces
246 |
247 | # creates two (num_faces, 3) tensors where the rows are an XYZ vector representing one of the edge vectors
248 | edge_vectors_1 = tf.gather(vertices, faces[:, 1]) - tf.gather(
249 | vertices, faces[:, 0]
250 | )
251 | edge_vectors_2 = tf.gather(vertices, faces[:, 2]) - tf.gather(
252 | vertices, faces[:, 1]
253 | )
254 |
255 | # computes the cross product of the two edge arrays dim (num_faces, 3)
256 | edge_cross = tf.linalg.cross(edge_vectors_1, edge_vectors_2)
257 |
258 | # computes the magnitude of the adge_cross vector dim (numfaces)
259 | edge_cross_mag = tf.norm(edge_cross, axis=1)
260 |
261 | # the unit normal is the cross product divided by its magnitude dim (num_faces, 3)
262 | face_unit_normals = edge_cross / edge_cross_mag[:, None]
263 |
264 | # a triangle's area is equal to 1/2 * mag(edge cross product) dim (num_faces)
265 | face_areas = 0.5 * edge_cross_mag
266 |
267 | return face_unit_normals, face_areas
268 |
269 | def sample_surface(self, vertices, count):
270 | """
271 | ARGS:
272 | verticies: tensor of shape (num_verticies, 3) XYZ coordinate associated with each vertex
273 | self.faces: np array (num_faces, 3) set of three row indexesassociated with each face
274 | self.face_areas: np array (num_faces) hlding the area of each face
275 | self.face_unit_normals: np array (num_faces, 3) representing the XYZ vector of the unti normal for the face
276 | count: number of points to sample
277 |
278 | Uses:
279 | https://mathworld.wolfram.com/TrianglePointPicking.html
280 |
281 | returns:
282 | sample_points: tf (count, 3) XYZ coordinate for randomly sampled points accross the mesh
283 | sample_normals: tf (count, 3) XYZ norms for the selected points
284 | """
285 |
286 | faces = self.faces
287 | face_unit_normals, face_areas = self.generate_face_areas_normals(vertices)
288 |
289 | # normalize the face areas by the total area
290 | total_face_area = tf.math.reduce_sum(face_areas)
291 | face_areas = face_areas / total_face_area
292 |
293 | # Creates a probability distribution from the face areas
294 | face_distribution = tfp.distributions.Categorical(probs=face_areas)
295 |
296 | # samples the distribution count number of times, then gets the face row value for the relevant face
297 | face_to_sample = face_distribution.sample(count)
298 | face_index = face_to_sample # (count)
299 | face_to_sample = tf.gather(faces, face_to_sample) # (count, 3)
300 |
301 | # sets XYZ "origins" for each triangle as the 0th index vertex (count, 3)
302 | origin = tf.gather(
303 | vertices,
304 | face_to_sample[:, 0],
305 | )
306 |
307 | # defines the two edges that, with the origin, define the triangle (count, 3)
308 | edge_1 = tf.gather(vertices, face_to_sample[:, 1]) - origin
309 | edge_2 = tf.gather(vertices, face_to_sample[:, 2]) - origin
310 |
311 | # stacks the two edge matricies together, dim (count, 2, 3)
312 | edges = tf.stack([edge_1, edge_2], axis=1)
313 |
314 | # computes two values between 0 and 1 that will scale the edge vectors and then summed to compute the points
315 | # dim (count, 2)
316 | edge_weights = tf.random.uniform(shape=(count, 2))
317 |
318 | # some points would be outside the triangle. if the sum is less than one then it is insde and thus
319 | # outside_triangle is true. dim (count)
320 | outside_triange = tf.math.reduce_sum(edge_weights, axis=1) > 1.0
321 |
322 | # remaps the points that are outside the trianle inside of the triangle
323 | edge_weights = edge_weights - tf.expand_dims(
324 | tf.cast(outside_triange, dtype=tf.float32), axis=1
325 | )
326 | edge_weights = tf.math.abs(edge_weights)
327 |
328 | # computes a sample vector as the weighted sum of the edge vectors using the random weights from above
329 | # dim (count, 3)
330 | sample_vector = tf.math.reduce_sum(
331 | edges * tf.expand_dims(edge_weights, axis=2), axis=1
332 | )
333 |
334 | # sample points are the displacement vector plus the origin
335 | # dim (count, 3)
336 | sample_points = sample_vector + origin
337 |
338 | # gather the normal for each point from the face it was sampled from
339 | sample_normals = tf.gather(face_unit_normals, face_index)
340 |
341 | return sample_points, sample_normals
342 |
--------------------------------------------------------------------------------
/source/mesh/MeshChecker.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from .Mesh import Mesh
3 |
4 |
5 | class MeshChecker:
6 | def __init__(self, mesh: Mesh) -> None:
7 | self.mesh = mesh
8 |
9 | def _check_vertices(self) -> bool:
10 | """Check the validity of the mesh's vertices field."""
11 |
12 | # Check the shape.
13 | if self.mesh.vertices.shape[1] != 3:
14 | return False
15 |
16 | # Create a list of vertices seen in faces.
17 | vertices_seen_in_faces = set()
18 | for vertex_a, vertex_b, vertex_c in self.mesh.faces:
19 | vertices_seen_in_faces.add(vertex_a)
20 | vertices_seen_in_faces.add(vertex_b)
21 | vertices_seen_in_faces.add(vertex_c)
22 |
23 | # Create a list of vertices seen in non-masked edges.
24 | vertices_seen_in_edges = set()
25 | for edge_key, edge in enumerate(self.mesh.edges):
26 | # Skip masked edges.
27 | if not self.mesh.edge_mask[edge_key]:
28 | continue
29 | vertices_seen_in_edges.add(edge[0])
30 | vertices_seen_in_edges.add(edge[1])
31 |
32 | # Check each vertex's validity.
33 | for vertex_key, xyz in enumerate(self.mesh.vertices):
34 | # Skip masked vertices.
35 | if not self.mesh.vertex_mask[vertex_key]:
36 | continue
37 |
38 | # The vertex's position should not include NaN.
39 | if np.isnan(xyz).any():
40 | return False
41 |
42 | # The vertex should appear in at least one edge.
43 | if vertex_key not in vertices_seen_in_edges:
44 | return False
45 |
46 | # The vertex should appear in at least one face.
47 | if vertex_key not in vertices_seen_in_faces:
48 | return False
49 |
50 | # The vertex should appear in vertex_to_edges.
51 | if self.mesh.vertex_to_edges[vertex_key] is None:
52 | return False
53 |
54 | return True
55 |
56 | def _check_edges(self) -> bool:
57 | """Check the validity of the mesh's edges field."""
58 |
59 | # Check the shape.
60 | if self.mesh.edges.shape[1] != 2:
61 | return False
62 |
63 | # Non-masked edges should not include masked vertices.
64 | for edge_key, edge in enumerate(self.mesh.edges):
65 | # Skip masked edges.
66 | if not self.mesh.edge_mask[edge_key]:
67 | continue
68 | a_masked = not self.mesh.vertex_mask[edge[0]]
69 | b_masked = not self.mesh.vertex_mask[edge[1]]
70 | if a_masked or b_masked:
71 | return False
72 |
73 | return True
74 |
75 | def _check_edge_to_neighbors_and_edge_lookup(self) -> bool:
76 | """Check the validity of the mesh's edge_to_neighbors and edge_lookup
77 | fields.
78 | """
79 |
80 | # Check the shapes.
81 | num_edges = self.mesh.edges.shape[0]
82 | if self.mesh.edge_to_neighbors.shape != (num_edges, 4):
83 | return False
84 | if self.mesh.edge_lookup.shape != (num_edges, 4):
85 | return False
86 |
87 | for edge_key, neighbors in enumerate(self.mesh.edge_to_neighbors):
88 | # Skip masked edges.
89 | if not self.mesh.edge_mask[edge_key]:
90 | continue
91 |
92 | # Confirm that edge_to_neighbors and edge_lookup agree.
93 | # Check that each neighbor's nth neighbor is the original edge,
94 | # where n = lookup[neighbor index].
95 | lookup = self.mesh.edge_lookup[edge_key, :]
96 | for neighbor_index, neighbor in enumerate(neighbors):
97 | expected_edge_key = self.mesh.edge_to_neighbors[
98 | neighbor, lookup[neighbor_index]
99 | ]
100 | if edge_key != expected_edge_key:
101 | return False
102 |
103 | # Make sure no neighbors are masked.
104 | neighbors = self.mesh.edge_to_neighbors[edge_key, :]
105 | for neighbor_key in neighbors:
106 | if not self.mesh.edge_mask[neighbor_key]:
107 | return False
108 |
109 | return True
110 |
111 | def _check_vertex_to_edges(self):
112 | """Check the validity of the mesh's vertex_to_edges field."""
113 |
114 | for vertex_key, edge_connections in enumerate(self.mesh.vertex_to_edges):
115 | # A masked vertex should not have EdgeConnections.
116 | if edge_connections is None:
117 | if self.mesh.vertex_mask[vertex_key]:
118 | return False
119 | continue
120 |
121 | # Check the validity of the EdgeConnections.
122 | for edge_connection in edge_connections:
123 | edge_key = edge_connection.edge_index
124 | index_in_edge = edge_connection.index_in_edge
125 |
126 | # EdgeConnections should not include masked edges.
127 | if not self.mesh.edge_mask[edge_key]:
128 | return False
129 |
130 | # The EdgeConnection should agree with what's in edges.
131 | if self.mesh.edges[edge_key, index_in_edge] != vertex_key:
132 | return False
133 |
134 | return True
135 |
136 | def _check_edge_mask(self) -> bool:
137 | """Check the validity of the mesh's edge_mask field."""
138 |
139 | # Check the shape.
140 | num_edges = self.mesh.edges.shape[0]
141 | if self.mesh.edge_mask.shape != (num_edges,):
142 | return False
143 |
144 | # Check that the edge mask agrees with num_edges.
145 | if self.mesh.num_edges != np.sum(self.mesh.edge_mask):
146 | return False
147 |
148 | return True
149 |
150 | def _check_vertex_mask(self) -> bool:
151 | """Check the validity of the mesh's vertex_mask field."""
152 |
153 | num_vertices = self.mesh.vertices.shape[0]
154 | if self.mesh.vertex_mask.shape != (num_vertices,):
155 | return False
156 |
157 | return True
158 |
159 | def check_validity(self) -> bool:
160 | return (
161 | True
162 | and self._check_vertices()
163 | and self._check_edges()
164 | and self._check_edge_to_neighbors_and_edge_lookup()
165 | and self._check_vertex_to_edges()
166 | and self._check_edge_mask()
167 | and self._check_vertex_mask()
168 | )
169 |
--------------------------------------------------------------------------------
/source/mesh/Obj.py:
--------------------------------------------------------------------------------
1 | import trimesh
2 | import numpy as np
3 | from typing import Tuple
4 | import os
5 |
6 |
7 | class Obj:
8 | """This just makes it easier to save and load obj files."""
9 |
10 | @staticmethod
11 | def save(file_name: str, vertices: np.ndarray, faces: np.ndarray) -> None:
12 | os.makedirs(os.path.dirname(file_name), exist_ok=True)
13 | with open(file_name, "w") as f:
14 | mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
15 | f.write(trimesh.exchange.obj.export_obj(mesh))
16 |
17 | @staticmethod
18 | def load(file_name: str) -> Tuple[np.ndarray, np.ndarray]:
19 | with open(file_name, "r") as f:
20 | mesh = trimesh.exchange.obj.load_obj(f)
21 | return np.float32(mesh["vertices"]), mesh["faces"]
--------------------------------------------------------------------------------
/source/mesh/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/mesh/__init__.py
--------------------------------------------------------------------------------
/source/mesh/__tests__/TestMesh.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import trimesh
3 | import numpy as np
4 | import tensorflow as tf
5 | from typing import Tuple
6 | from ..MeshChecker import MeshChecker
7 | from ..Mesh import Mesh
8 | from ...layers.pooling.CollapseSnapshot import CollapseSnapshot
9 | from ...layers.pooling.collapse_edge import collapse_edge
10 |
11 |
12 | class TestMesh(unittest.TestCase):
13 | def load_obj(self, file_name: str) -> Tuple[np.ndarray]:
14 | with open(file_name, "r") as f:
15 | mesh = trimesh.exchange.obj.load_obj(f)
16 | return np.float32(mesh["vertices"]), mesh["faces"]
17 |
18 | def test_icosahedron_smoke(self):
19 | vertices, faces = self.load_obj("data/objs/icosahedron.obj")
20 | Mesh(vertices, faces)
21 |
22 | def test_icosahedron_edge_count(self):
23 | vertices, faces = self.load_obj("data/objs/icosahedron.obj")
24 | mesh = Mesh(vertices, faces)
25 | self.assertEqual(mesh.edges.shape, (30, 2))
26 |
27 | def test_icosahedron_vertex_degree(self):
28 | vertices, faces = self.load_obj("data/objs/icosahedron.obj")
29 | mesh = Mesh(vertices, faces)
30 |
31 | vertex_degrees = {}
32 | for edge_index in range(mesh.edges.shape[0]):
33 | v0, v1 = mesh.edges[edge_index, :]
34 | vertex_degrees[v0] = vertex_degrees.get(v0, 0) + 1
35 | vertex_degrees[v1] = vertex_degrees.get(v1, 0) + 1
36 |
37 | for degree in vertex_degrees.values():
38 | self.assertEqual(degree, 5)
39 |
40 | def test_icosahedron_validity(self):
41 | vertices, faces = self.load_obj("data/objs/icosahedron.obj")
42 | mesh = Mesh(vertices, faces)
43 | checker = MeshChecker(mesh)
44 | self.assertTrue(checker.check_validity())
45 |
46 | def test_tetrahedron_validity(self):
47 | vertices, faces = self.load_obj("data/objs/tetrahedron.obj")
48 | mesh = Mesh(vertices, faces)
49 | checker = MeshChecker(mesh)
50 | self.assertTrue(checker.check_validity())
51 |
52 | def test_collapse_masked_elements(self):
53 | vertices, faces = self.load_obj("data/objs/icosahedron.obj")
54 | mesh = Mesh(vertices, faces)
55 | snapshot = CollapseSnapshot(mesh)
56 | checker = MeshChecker(mesh)
57 | for edge_key in range(mesh.edges.shape[0]):
58 | collapse_edge(mesh, edge_key, snapshot)
59 | mesh.collapse_masked_elements()
60 | self.assertTrue(checker.check_validity())
61 |
62 | def test_face_areas_normals(self):
63 | vertices, faces = self.load_obj("data/objs/icosahedron.obj")
64 | mesh = Mesh(vertices, faces)
65 | normals, areas = mesh.generate_face_areas_normals(mesh.vertices)
66 | checker = MeshChecker(mesh)
67 | self.assertTrue(np.abs(np.mean(areas.numpy()) - 4787.286) < 0.01)
68 | self.assertTrue(checker.check_validity)
69 |
70 | def test_sample(self):
71 | vertices, faces = self.load_obj("data/objs/icosahedron.obj")
72 | mesh = Mesh(vertices, faces)
73 | points, normals = mesh.sample_surface(tf.convert_to_tensor(mesh.vertices), 1001)
74 | self.assertTrue(np.abs(np.mean(points.numpy())) < 5)
75 | self.assertTrue(np.abs(np.mean(normals.numpy())) < 0.5)
76 |
77 | def test_remesh(self):
78 | vertices_old, faces_old = self.load_obj("data/objs/cylinder.obj")
79 | mesh_old = Mesh(vertices_old, faces_old)
80 | mesh_new = mesh_old.remesh()
81 | vertices_new = mesh_new.vertices
82 | faces_new = mesh_new.faces
83 | checker = MeshChecker(mesh_new)
84 | self.assertTrue(checker.check_validity())
85 | mesh_for_saving = trimesh.Trimesh(vertices=vertices_new, faces=faces_new)
86 | with open("tmp_out_test_remesh.obj", "w") as f:
87 | f.write(trimesh.exchange.obj.export_obj(mesh_for_saving))
--------------------------------------------------------------------------------
/source/mesh/__tests__/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/mesh/__tests__/__init__.py
--------------------------------------------------------------------------------
/source/mesh/remesh.py:
--------------------------------------------------------------------------------
1 | import os
2 | import uuid
3 | import trimesh
4 | import numpy as np
5 |
6 | MANIFOLD_SOFTWARE_DIR = "Manifold/build"
7 |
8 |
9 | def remesh(
10 | vertices: np.ndarray,
11 | faces: np.ndarray,
12 | num_faces=2000,
13 | ):
14 | # Write the original mesh as OBJ.
15 | original_file = random_file_name("obj")
16 | with open(original_file, "w") as f:
17 | mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
18 | f.write(trimesh.exchange.obj.export_obj(mesh))
19 |
20 | # Create a manifold of the original file.
21 | manifold_file = random_file_name("obj")
22 | manifold_script_path = os.path.join(MANIFOLD_SOFTWARE_DIR, "manifold")
23 | cmd = f"{manifold_script_path} {original_file} {manifold_file}"
24 | os.system(cmd)
25 |
26 | # Simplify the manifold.
27 | simplified_file = random_file_name("obj")
28 | simplify_script_path = os.path.join(MANIFOLD_SOFTWARE_DIR, "simplify")
29 | cmd = (
30 | f"{simplify_script_path} -i {manifold_file} -o {simplified_file} -f {num_faces}"
31 | )
32 | os.system(cmd)
33 |
34 | # Read the simplified manifold.
35 | with open(simplified_file, "r") as f:
36 | mesh = trimesh.exchange.obj.load_obj(f)
37 |
38 | # Prevent file spam.
39 | os.remove(original_file)
40 | os.remove(manifold_file)
41 | os.remove(simplified_file)
42 |
43 | return mesh["vertices"], mesh["faces"]
44 |
45 |
46 | def random_file_name(ext, prefix="tmp_"):
47 | return f"{prefix}{uuid.uuid4()}.{ext}"
--------------------------------------------------------------------------------
/source/model/PointToMeshModel.py:
--------------------------------------------------------------------------------
1 | from tensorflow.keras import Model
2 | from ..layers.composite.Encoder import Encoder
3 | from ..layers.composite.Decoder import Decoder
4 | from ..layers.convolution.MeshConvolution import MeshConvolution
5 | from ..mesh.Mesh import Mesh
6 | from typing import List
7 | import tensorflow as tf
8 |
9 |
10 | class PointToMeshModel(Model):
11 | def __init__(self, initial_num_edges: int, pooling: List[float]) -> None:
12 | super(PointToMeshModel, self).__init__()
13 | assert len(pooling) == 6
14 | scaled_pooling = [None if p is None else initial_num_edges * p for p in pooling]
15 | self.encoder = Encoder((6, 16, 32, 64, 64, 128), 1, 3, 0.01, scaled_pooling)
16 | self.decoder = Decoder((64, 64, 32, 16, 6, 6), 1, 1, 0.01, scaled_pooling)
17 | self.batch_normalization = tf.keras.layers.BatchNormalization()
18 | initializer = tf.keras.initializers.RandomUniform(-1e-8, 1e-8)
19 | self.final_convolution = MeshConvolution(6, initializer, initializer)
20 |
21 | def call(self, mesh: Mesh, fixed_input_features: tf.Tensor):
22 | assert tf.rank(fixed_input_features) == 2 and fixed_input_features.shape[1] == 6
23 |
24 | encoding, snapshots = self.encoder(mesh, fixed_input_features)
25 | decoding = self.decoder(mesh, encoding, snapshots)
26 | normalized = self.batch_normalization(decoding, training=True)
27 | output = self.final_convolution(mesh, normalized)
28 |
29 | return output
--------------------------------------------------------------------------------
/source/model/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/model/__init__.py
--------------------------------------------------------------------------------
/source/model/__tests__/TestGetVertexFeatures.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import trimesh
3 | import tensorflow as tf
4 | import numpy as np
5 | from ...mesh.Mesh import Mesh
6 | from ..get_vertex_features import get_vertex_features, naive_get_vertex_features
7 |
8 |
9 | class TestGetVertexFeatures(unittest.TestCase):
10 | def setUp(self) -> None:
11 | with open("data/objs/icosahedron.obj", "r") as f:
12 | mesh = trimesh.exchange.obj.load_obj(f)
13 | self.mesh = Mesh(mesh["vertices"], mesh["faces"])
14 |
15 | def test_against_naive(self):
16 | num_edges = self.mesh.edges.shape[0]
17 | features = tf.random.uniform((num_edges, 6))
18 |
19 | fast = get_vertex_features(self.mesh, features)
20 | naive = naive_get_vertex_features(self.mesh, features)
21 |
22 | tf.debugging.assert_near(fast, naive)
23 |
--------------------------------------------------------------------------------
/source/model/__tests__/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/model/__tests__/__init__.py
--------------------------------------------------------------------------------
/source/model/get_vertex_features.py:
--------------------------------------------------------------------------------
1 | from ..mesh.Mesh import Mesh
2 | from ..layers.feature import features_valid
3 | import tensorflow as tf
4 | import numpy as np
5 |
6 |
7 | def get_vertex_features(mesh: Mesh, features: tf.Tensor):
8 | """Convert edge features in shape (num_edges, 6) to vertex features in shape
9 | (num_vertices, 3).
10 | """
11 | assert features_valid(mesh, features)
12 | assert features.shape[1] == 6
13 |
14 | # Reshape features into the following shape:
15 | # (num_edges, num_vertices_per_edge = 2, dimensions_per_vertex = 3)
16 | edge_features = tf.reshape(features, (-1, 2, 3))
17 |
18 | # Now, pad the first two dimensions with slices of zeros.
19 | # This will be needed to handle variable vertex degrees using gather_nd.
20 | edge_features = tf.pad(edge_features, ((0, 1), (0, 1), (0, 0)))
21 |
22 | # Since TensorFlow doesn't have NumPy like indexing, it's a bit harder to do
23 | # this than in PyTorch.
24 | gathered_vertex_features = tf.gather_nd(edge_features, mesh.vertex_to_edges_tensor)
25 | gathered_vertex_features = tf.reshape(
26 | gathered_vertex_features, (mesh.vertices.shape[0], mesh.max_vertex_degree, 3)
27 | )
28 |
29 | # Get a weighted average of the features accumulated from the edges.
30 | vertex_features = tf.math.reduce_sum(gathered_vertex_features, axis=1)
31 | vertex_features /= mesh.vertex_to_degree[:, None]
32 |
33 | return vertex_features
34 |
35 |
36 | def naive_get_vertex_features(mesh: Mesh, features: tf.Tensor):
37 | """This is a naive version of get_vertex_features used for testing."""
38 | assert features_valid(mesh, features)
39 | assert features.shape[1] == 6
40 |
41 | # Create a list of features for each vertex.
42 | vertex_features = [[] for _ in range(mesh.vertices.shape[0])]
43 | for edge_key in range(features.shape[0]):
44 | left_vertex, right_vertex = mesh.edges[edge_key, :]
45 | vertex_features[left_vertex].append(features[edge_key, :3])
46 | vertex_features[right_vertex].append(features[edge_key, 3:])
47 |
48 | # Use the mean of each vertex's list of features as that vertex's feature.
49 | vertex_features = [tf.stack(vf) for vf in vertex_features]
50 | vertex_features = [tf.math.reduce_mean(vf, axis=0) for vf in vertex_features]
51 | return tf.stack(vertex_features)
--------------------------------------------------------------------------------
/source/options/options.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | DEFAULTS = {
4 | # The point cloud that's fitted.
5 | "point_cloud": "data/point_clouds/elephant.pwn",
6 | # The number of times remeshing/subdivision happens.
7 | "num_subdivisions": 6,
8 | # The number of iterations between each remeshing/subdivision.
9 | "num_iterations": 1000,
10 | # Each subdivision multiplies the number of faces by this.
11 | "subdivision_multiplier": 1.5,
12 | # The maximum number of faces that subdivision is allowed to yield.
13 | "max_num_faces": 10000,
14 | # The initial number of faces used for optimization.
15 | "initial_num_faces": 1000,
16 | # An optional initial mesh.
17 | "initial_mesh": None,
18 | # The folder where the results are saved.
19 | "save_location": "results",
20 | # how often to run beamgap loss if -1 then no beam gap loss
21 | "beamgap_modulo": -1,
22 | # how often to save objs
23 | "obj_save_modulo": 5,
24 | # range to lineralyinterp between when computing samples
25 | "min_num_samples": 10000,
26 | "max_num_samples": 16000,
27 | "pooling": [None, None, None, None, None, None],
28 | }
29 |
30 |
31 | def load_options(argv: list):
32 | if len(argv) == 1:
33 | return DEFAULTS
34 | if len(argv) != 2:
35 | raise Exception(
36 | "The only (optional) argument should be the settings JSON file."
37 | )
38 | with open(argv[1], "r") as f:
39 | options = json.load(f)
40 |
41 | for key in options:
42 | if key not in DEFAULTS:
43 | raise Exception(f'Unknown setting "{key}"')
44 | return {**DEFAULTS, **options}
--------------------------------------------------------------------------------
/source/script_modules/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/script_modules/__init__.py
--------------------------------------------------------------------------------
/source/script_modules/create_convex_hull.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import numpy as np
3 | import trimesh
4 |
5 | # Get the input file path.
6 | parser = argparse.ArgumentParser(description="Create a convex hull.")
7 | parser = argparse.ArgumentParser()
8 | parser.add_argument(
9 | "--input", type=argparse.FileType("r", encoding="UTF-8"), required=True
10 | )
11 | parser.add_argument(
12 | "--output", type=argparse.FileType("w", encoding="UTF-8"), required=True
13 | )
14 | args = parser.parse_args()
15 |
16 | # Load the points into NumPy.
17 | points = np.loadtxt(args.input)
18 | convex_hull = trimesh.convex.convex_hull(points[:, :3])
19 |
20 | # Save an OBJ file.
21 | obj = trimesh.exchange.obj.export_obj(convex_hull)
22 | args.output.write(obj)
23 |
--------------------------------------------------------------------------------
/source/script_modules/make_c4d_point_cloud_ascii.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | # Load the point cloud.
4 | file = "data/point_clouds/tiki.pwn"
5 | point_cloud_np = np.loadtxt(file)[:, :3]
6 |
7 | # Randomly sample n points.
8 | max_num_points = point_cloud_np.shape[0] / 2
9 | point_cloud_np = point_cloud_np[
10 | np.random.choice(np.arange(point_cloud_np.shape[0]), 1000, replace=False), :
11 | ]
12 |
13 | # Write to C4D's ASCII format (for C4D Structure Manager).
14 | with open("tmp_c4d.txt", "w") as f:
15 | f.write("Point X Y Z\n")
16 | for row in range(point_cloud_np.shape[0]):
17 | f.write(
18 | f"{row}\t{point_cloud_np[row, 0]} cm\t{point_cloud_np[row, 1]} cm\t{point_cloud_np[row, 2]} cm\n"
19 | )
20 |
--------------------------------------------------------------------------------
/source/script_modules/remesh_obj.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import numpy as np
3 | import trimesh
4 | from ..mesh.remesh import remesh
5 |
6 | # Get the input file path.
7 | parser = argparse.ArgumentParser(description="Remesh an obj.")
8 | parser = argparse.ArgumentParser()
9 | parser.add_argument(
10 | "--input", type=argparse.FileType("r", encoding="UTF-8"), required=True
11 | )
12 | parser.add_argument(
13 | "--output", type=argparse.FileType("w", encoding="UTF-8"), required=True
14 | )
15 | args = parser.parse_args()
16 |
17 | # Load the obj.
18 | mesh = trimesh.exchange.obj.load_obj(args.input)
19 | vertices = mesh["vertices"]
20 | faces = mesh["faces"]
21 |
22 | # Remesh the obj.
23 | remeshed_vertices, remeshed_faces = remesh(vertices, faces)
24 |
25 | # Save an OBJ file.
26 | mesh = trimesh.Trimesh(vertices=remeshed_vertices, faces=remeshed_faces)
27 | obj = trimesh.exchange.obj.export_obj(mesh)
28 | args.output.write(obj)
29 |
--------------------------------------------------------------------------------
/source/script_modules/train_model.py:
--------------------------------------------------------------------------------
1 | import time
2 | import sys
3 | import tensorflow as tf
4 | import numpy as np
5 | from tensorflow.python.keras.backend import dtype
6 | import trimesh
7 | import colorama
8 | import os
9 | from colorama import Fore, Back, Style
10 | from ..mesh.Mesh import Mesh
11 | from ..mesh.Obj import Obj
12 | from ..model.PointToMeshModel import PointToMeshModel
13 | from ..model.get_vertex_features import get_vertex_features
14 | from ..loss.ChamferLossLayer import ChamferLossLayer
15 | from ..loss.loss import BeamGapLossLayer, discrete_project
16 | from ..loss.ConvergenceDetector import ConvergenceDetector
17 | from ..mesh.remesh import remesh
18 | from ..options.options import load_options
19 |
20 | # This makes Colorama (terminal colors) work on Windows.
21 | colorama.init()
22 |
23 | # Set up CUDA.
24 | physical_devices = tf.config.list_physical_devices("GPU")
25 | if len(physical_devices) > 0:
26 | tf.config.experimental.set_memory_growth(physical_devices[0], True)
27 |
28 | # Load the options and the point cloud.
29 | options = load_options(sys.argv)
30 | with open(options["point_cloud"], "r") as f:
31 | point_cloud_np = np.loadtxt(f)[:, :3]
32 | point_cloud_tf = tf.convert_to_tensor(point_cloud_np, dtype=tf.float32)
33 |
34 | # Create a function for saving meshes.
35 | def save_mesh(file_name, vertices, faces):
36 | Obj.save(os.path.join(options["save_location"], file_name), vertices, faces)
37 |
38 |
39 | # Create the mesh.
40 | if options["initial_mesh"]:
41 | remeshed_vertices, remeshed_faces = Obj.load(options["initial_mesh"])
42 | else:
43 | convex_hull = trimesh.convex.convex_hull(point_cloud_np)
44 | remeshed_vertices, remeshed_faces = remesh(
45 | convex_hull.vertices, convex_hull.faces, options["initial_num_faces"]
46 | )
47 | save_mesh("tmp_initial_mesh.obj", remeshed_vertices, remeshed_faces)
48 |
49 | # Create and train the model.
50 | chamfer_loss = ChamferLossLayer()
51 | beam_loss = BeamGapLossLayer(discrete_project)
52 | optimizer = tf.keras.optimizers.Adam(learning_rate=0.00005)
53 | num_subdivisions = options["num_subdivisions"]
54 | new_vertices = None
55 | for subdivision_level in range(num_subdivisions):
56 | # Create a new model at each subdivision level.
57 | # This is because the learned weights don't probably don't carry over to
58 | # different initial positions and resolutions.
59 | chamfer_convergence = ConvergenceDetector()
60 | beam_convergence = ConvergenceDetector()
61 |
62 | # Subdivide the mesh if beyond the first level.
63 | if subdivision_level != 0:
64 | if new_vertices is None:
65 | raise Exception("Could not find vertices to subdivide.")
66 | new_num_faces = min(
67 | options["max_num_faces"],
68 | options["subdivision_multiplier"] * remeshed_faces.shape[0],
69 | )
70 | print(
71 | f"{Back.MAGENTA}Remeshing to {int(new_num_faces)} faces.{Style.RESET_ALL}"
72 | )
73 | remeshed_vertices, remeshed_faces = remesh(
74 | new_vertices.numpy(), remeshed_faces, new_num_faces
75 | )
76 | else:
77 | print(
78 | f"{Back.MAGENTA}Starting with {remeshed_faces.shape[0]} faces.{Style.RESET_ALL}"
79 | )
80 | mesh = Mesh(remeshed_vertices, remeshed_faces)
81 | model = PointToMeshModel(mesh.edges.shape[0], options["pooling"])
82 |
83 | # Create the random features.
84 | in_features = tf.random.uniform((mesh.edges.shape[0], 6), -0.5, 0.5)
85 |
86 | old_vertices = tf.convert_to_tensor(remeshed_vertices, dtype=tf.float32)
87 | num_iterations = options["num_iterations"]
88 | for iteration in range(num_iterations):
89 | iteration_start_time = time.time()
90 |
91 | converged = False
92 | with tf.GradientTape() as tape:
93 | # Get new vertex positions by calling the model.
94 | features = model(mesh, in_features)
95 | new_vertices = old_vertices + get_vertex_features(mesh, features)
96 |
97 | # Calculate loss.
98 | num_samples = int(
99 | options["min_num_samples"]
100 | + (iteration / options["num_iterations"])
101 | * (options["max_num_samples"] - options["min_num_samples"])
102 | )
103 | surface_sample = mesh.sample_surface(new_vertices, num_samples)
104 | beamgap_modulo = options["beamgap_modulo"]
105 | if beamgap_modulo == -1:
106 | use_beamgap_loss = False
107 | else:
108 | use_beamgap_loss = iteration % beamgap_modulo == 0
109 | if use_beamgap_loss:
110 | beam_loss.update_points_masks(mesh, new_vertices, point_cloud_tf)
111 | total_loss = 0.01 * beam_loss(mesh, new_vertices)
112 | converged = beam_convergence.step(total_loss.numpy().item())
113 | else:
114 | total_loss = chamfer_loss(
115 | surface_sample[0], point_cloud_tf, num_samples
116 | )
117 | converged = chamfer_convergence.step(total_loss.numpy().item())
118 |
119 | # Apply gradients.
120 | gradients = tape.gradient(total_loss, model.trainable_variables)
121 | optimizer.apply_gradients(zip(gradients, model.trainable_variables))
122 |
123 | # Save the obj every few iterations.
124 | save_modulo = options["obj_save_modulo"]
125 | if iteration % save_modulo == 0 or converged or iteration == num_iterations - 1:
126 | save_mesh(
127 | f"tmp_{str(subdivision_level).zfill(2)}_{str(iteration).zfill(3)}.obj",
128 | new_vertices.numpy(),
129 | remeshed_faces,
130 | )
131 |
132 | # Log a progress update.
133 | loss_type = "Beam-gap" if use_beamgap_loss else "Chamfer"
134 | message = [
135 | f"{Back.WHITE}{Fore.BLACK}"
136 | f" {subdivision_level + 1}/{num_subdivisions} & {iteration + 1}/{num_iterations}",
137 | f"{Style.RESET_ALL}",
138 | f"{loss_type} Loss: {total_loss.numpy().item()},",
139 | f"Time: {time.time() - iteration_start_time}",
140 | ]
141 | print(" ".join(message))
142 | if converged:
143 | print(
144 | f"{Back.MAGENTA}Converged at iteration {iteration + 1}/{num_iterations}.{Style.RESET_ALL}"
145 | )
146 | break
147 |
148 | print("Done.")
149 |
--------------------------------------------------------------------------------