├── .gitignore
├── README.md
├── docker
├── Dockerfile
└── environment.yaml
├── environment.yaml
├── scripts
├── arguments.py
├── check_watertightness.py
├── convert_to_watertight.py
├── make_mesh_watertight.py
├── simplification.mlx
└── utils.py
├── setup.py
└── watertight_transformer
├── __init__.py
├── base.py
├── datasets
├── __init__.py
└── model_collections.py
├── external
├── __init__.py
├── libfusioncpu
│ ├── LICENSE
│ ├── README.md
│ ├── __init__.py
│ ├── cyfusion.cpp
│ ├── cyfusion.pyx
│ ├── fusion.cpp
│ └── fusion.h
├── libfusiongpu
│ ├── CMakeLists.txt
│ ├── LICENSE
│ ├── README.md
│ ├── __init__.py
│ ├── cyfusion.cpp
│ ├── cyfusion.pyx
│ ├── fusion.cu
│ ├── fusion.h
│ ├── fusion_zach_tvl1.cu
│ └── gpu_common.h
├── libmcubes
│ ├── LICENSE
│ ├── README.rst
│ ├── __init__.py
│ ├── exporter.py
│ ├── marchingcubes.cpp
│ ├── marchingcubes.h
│ ├── mcubes.cpp
│ ├── mcubes.pyx
│ ├── pyarray_symbol.h
│ ├── pyarraymodule.h
│ ├── pywrapper.cpp
│ └── pywrapper.h
├── libmesh
│ ├── .gitignore
│ ├── README.md
│ ├── __init__.py
│ ├── inside_mesh.py
│ └── triangle_hash.pyx
└── librender
│ ├── README.md
│ ├── __init__.py
│ ├── offscreen.cpp
│ ├── offscreen.h
│ ├── pyrender.cpp
│ └── pyrender.pyx
├── manifoldplus.py
├── tsdf_fusion.py
└── utils.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | build
3 | dist
4 | *.egg-info
5 | *.so
6 | *.py.swp
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Watertight and Simplified Meshes through TSDF Fusion and ManifoldPlus
2 |
3 | This repository contains a simple Python pipeline for obtaining watertight
4 | meshes from arbitrary triangual meshes. We provide a simple to use python
5 | wrapper for two methods that can be used for watertight conversion: (i)
6 | [mesh-fusion](https://github.com/davidstutz/mesh-fusion) by David Stutz and
7 | (ii) [ManifoldPlus](https://github.com/hjwdzh/ManifoldPlus) by Jingwei Huang.
8 | Our code is adapted by
9 | [mesh-fusion](https://github.com/davidstutz/mesh-fusion), which in turn uses on
10 | adapted versions of Gernot Riegler's
11 | [pyrender](https://github.com/griegler/pyrender) and
12 | [pyfusion](https://github.com/griegler/pyfusion); it also uses
13 | [PyMCubes](https://github.com/pmneila/PyMCubes).
14 |
15 | In case you use any of this code, please make sure to cite David's and Jingwei's work:
16 |
17 | @article{Stutz2018ARXIV,
18 | author = {David Stutz and Andreas Geiger},
19 | title = {Learning 3D Shape Completion under Weak Supervision},
20 | journal = {CoRR},
21 | volume = {abs/1805.07290},
22 | year = {2018},
23 | url = {http://arxiv.org/abs/1805.07290},
24 | }
25 |
26 | @article{huang2020manifoldplus,
27 | title={ManifoldPlus: A Robust and Scalable Watertight Manifold Surface Generation Method for Triangle Soups},
28 | author={Huang, Jingwei and Zhou, Yichao and Guibas, Leonidas},
29 | journal={arXiv preprint arXiv:2005.11621},
30 | year={2020}
31 | }
32 |
33 | Please also check the individual GitHub repositories within this repo for
34 | additional citations. Also check the corresponding project
35 | pages of [TSDF Fusion](http://davidstutz.de/projects/shape-completion/)
36 | amd [ManifoldPlus](https://github.com/hjwdzh/ManifoldPlus).
37 |
38 | Note that if you want to perform watertight conversion using the ManifoldPlus
39 | algorithm, you need to first compile the original code following the guidelines from
40 | [here](https://github.com/hjwdzh/ManifoldPlus). As soon as you have compiled their code
41 | you can directly pass the executable to any of the scripts in this repository.
42 |
43 |
44 | ## Installation & Dependencies
45 |
46 | This codebase has the following dependencies:
47 |
48 | - [numpy](https://numpy.org/doc/stable/user/install.html)
49 | - [cython](https://cython.readthedocs.io/en/latest/src/quickstart/build.html)
50 | - [pillow](https://pillow.readthedocs.io/en/stable/installation.html)
51 | - [pycollada](https://pycollada.readthedocs.io/en/latest/install.html)
52 | - [scipy](https://scipy.org/install/)
53 | - [trimesh](https://github.com/mikedh/trimesh)
54 | - [tqdm](https://github.com/tqdm/tqdm)
55 | - [h5py](https://www.h5py.org/)
56 | - [pymeshlab](https://pymeshlab.readthedocs.io/en/latest/)
57 |
58 | For the visualizations, we use [simple-3dviz](http://simple-3dviz.com).
59 | Note that
60 | [simple-3dviz](http://simple-3dviz.com) provides a lightweight and easy-to-use
61 | scene viewer using [wxpython](https://www.wxpython.org/).
62 | Finally, the code runs in headless mode by default, so you need to have `xvfb`
63 | installed (at least for the TSDF Fusion method).
64 | The simplest way to make sure that you have all dependencies in place is to use
65 | [conda](https://docs.conda.io/projects/conda/en/4.6.1/index.html). You can
66 | create a conda environment called ```mesh_fusion``` using
67 | ```
68 | conda env create -f environment.yaml
69 | conda activate mesh_fusion
70 | ```
71 |
72 | Next compile the extenstion modules. You can do this via
73 | ```
74 | python setup.py build_ext --inplace
75 | pip install -e .
76 | ```
77 |
78 | We also provide a Dockerfile that you can use to build a Docker image that contains all
79 | dependencies for running inside a container. You can build the Docker image using the
80 | following command:
81 | ```
82 | docker build -f docker/Dockerfile --build-arg UBUNTU_VERSION=18.04 --build-arg PYTHON_VERSION=3.8 --tag mesh_fusion_simple:latest .
83 | ```
84 |
85 | You can then run the Docker container in interactive mode, and use the scripts provided in the
86 | `mesh_fusion_simple` directory of the Docker image. Make sure to mount the path with the
87 | location of the original meshes:
88 | ```
89 | docker run -it --user $(id -u):$(id -g) --mount type=bind,source=[DATA_DIRECTORY_PATH],target=/data mesh_fusion_simple:latest
90 | ```
91 | Keep in mind that the ManifoldPlus executable is located inside the folder `/mesh_fusion_simple/scripts` of the docker image,
92 | named `manifoldplus`.
93 |
94 | ## Convert to watertight meshes
95 |
96 | To run our code, we provide the `convert_to_watertight.py` script. In order to
97 | run this script you only need to provide a path to the dataset directory, as
98 | well as the dataset type, namely ShapeNet, Dynamic FAUST, 3D-FRONT etc. For now,
99 | our code supports the 3D-FRONT, the ShapeNet, the Dynamic FAUST, the FreiHAND
100 | and the DeformingThings4D dataset. If you want to use another dataset, you simply
101 | need to implement a Dataset class that extends the `ModelCollection` class. For more
102 | details please refer to the `watertight_transformer/datasets/model_collections.py`
103 | file. To run the conversion script simply run
104 | ```
105 | python convert_to_watertight.py path_to_dataset_directory --dataset_type dataset_type
106 | ```
107 | Currently, our code only runs on CPU. However, you can lauch this script from
108 | using multiple CPU nodes in order to speed up the computation time. This script
109 | automatically checks whether a model has already been converted before initiating
110 | the transformation process. To run the code for multiple processes, you can use the
111 | `--num_cpus` flag like the following:
112 | ```
113 | python convert_to_watertight.py /orion/u/paschald/Datasets/ShapeNetCore.v1/ --watertight_method tsdf_fusion --category_tags 02691156 --dataset_type shapenet_v1 --unit_cube --num_cpus 10
114 | ```
115 | This script launches 10 CPU jobs. However, you can launch more or less
116 | depending on the availability of your resources.
117 |
118 | You can also use the `make_mesh_watertight.py` script to convert a single mesh
119 | to a watertight mesh by specifying its path as follows
120 | ```
121 | python make_mesh_watertight.py path_to_mesh path_to_output_directory --watertight_method tsdf_fusion
122 | ```
123 |
124 | Note that for both scripts you can set `--simplify` in order to simplify the
125 | final watertight mesh using
126 | [pymeshlab](https://pymeshlab.readthedocs.io/en/latest/). You can also rescale
127 | the watertight mesh, either using bounding box bounds (`--bbox`), or to make it
128 | fit inside a unit cube (`--unit_cube`).
129 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG UBUNTU_VERSION
2 |
3 | FROM ubuntu:${UBUNTU_VERSION}
4 |
5 | ARG PYTHON_VERSION
6 |
7 | # Install some basic utilities
8 | RUN apt-get update && apt-get install -y --no-install-recommends\
9 | meshlab \
10 | xvfb \
11 | libglew-dev \
12 | freeglut3-dev \
13 | build-essential \
14 | cmake \
15 | curl \
16 | ca-certificates \
17 | git \
18 | vim \
19 | && rm -rf /var/lib/apt/lists/*
20 |
21 |
22 | # Install miniconda
23 | RUN curl -o ~/miniconda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && \
24 | chmod +x ~/miniconda.sh && \
25 | ~/miniconda.sh -b -p /opt/conda && \
26 | rm ~/miniconda.sh && \
27 | /opt/conda/bin/conda install -y python=$PYTHON_VERSION && \
28 | /opt/conda/bin/conda clean -ya
29 |
30 |
31 | ENV PATH /opt/conda/bin:$PATH
32 |
33 | COPY docker/environment.yaml .
34 |
35 | RUN conda env update -f environment.yaml && conda clean -afy
36 |
37 | # Mesh fusion dependency installations
38 | COPY watertight_transformer mesh_fusion_simple/watertight_transformer
39 | COPY setup.py README.md mesh_fusion_simple/
40 | RUN cd mesh_fusion_simple && python setup.py build_ext --inplace && pip install -e .
41 | RUN cd mesh_fusion_simple && git clone https://github.com/hjwdzh/ManifoldPlus.git && \
42 | cd ManifoldPlus && git submodule update --init --recursive && mkdir build && \
43 | cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make -j8 && mkdir ../../scripts/ && \
44 | mv manifold ../../scripts/manifoldplus && cd ../../ && rm -rf ManifoldPlus
45 | COPY scripts mesh_fusion_simple/scripts
46 |
--------------------------------------------------------------------------------
/docker/environment.yaml:
--------------------------------------------------------------------------------
1 | name: base
2 | channels:
3 | - conda-forge
4 | - pytorch
5 | - defaults
6 | dependencies:
7 | - cython=0.29.21
8 | - numpy=1.19.1
9 | - scipy=1.7.3
10 | - pycollada=0.7.2
11 | - pillow=7.2.0
12 | - tqdm=4.50.2
13 | - trimesh=3.8.10
14 | - wxpython=4.0.7
15 | - h5py=3.6.0
16 | - pip
17 | - pip:
18 | - git+https://github.com/angeloskath/simple-3dviz.git
19 | - pymeshlab
20 | - pyvirtualdisplay
21 |
--------------------------------------------------------------------------------
/environment.yaml:
--------------------------------------------------------------------------------
1 | name: mesh_fusion
2 | channels:
3 | - conda-forge
4 | - pytorch
5 | - defaults
6 | dependencies:
7 | - cython=0.29.21
8 | - numpy=1.19.1
9 | - scipy=1.7.3
10 | - python=3.8
11 | - pycollada=0.7.2
12 | - pillow=7.2.0
13 | - tqdm=4.50.2
14 | - trimesh=3.8.10
15 | - wxpython=4.0.7
16 | - h5py=3.6.0
17 | - pymeshlab
18 | - pip
19 | - pip:
20 | - simple_3dviz==0.2.1
21 | - pyvirtualdisplay
22 |
--------------------------------------------------------------------------------
/scripts/arguments.py:
--------------------------------------------------------------------------------
1 | def add_tsdf_fusion_parameters(parser):
2 | parser.add_argument(
3 | "--n_views",
4 | type=int,
5 | default=100,
6 | help="Number of views per model"
7 | )
8 | parser.add_argument(
9 | "--image_size",
10 | type=lambda x: tuple(map(int, x.split(","))),
11 | default="640,640",
12 | help="The size of the depth images"
13 | )
14 | parser.add_argument(
15 | "--focal_point",
16 | type=lambda x: tuple(map(int, x.split(","))),
17 | default="640,640",
18 | help="The focal length in x and y direction"
19 | )
20 | parser.add_argument(
21 | "--principal_point",
22 | type=lambda x: tuple(map(int, x.split(","))),
23 | default="320,320",
24 | help="The principal point location in x and y direction"
25 | )
26 | parser.add_argument(
27 | "--resolution",
28 | type=int,
29 | default=256,
30 | help="The resolution for the fusion"
31 | )
32 | parser.add_argument(
33 | "--truncation_factor",
34 | type=int,
35 | default=15,
36 | help=("The truncation for fusion is derived as "
37 | "truncation_factor*voxel_size.")
38 | )
39 | parser.add_argument(
40 | "--depth_offset_factor",
41 | type=float,
42 | default=1.5,
43 | help=("The depth maps are offsetted using "
44 | "depth_offset_factor*voxel_size.")
45 | )
46 |
47 |
48 | def add_manifoldplus_parameters(parser):
49 | parser.add_argument(
50 | "--manifoldplus_script",
51 | default=None,
52 | help="Path to the script used for implemented the Manifold algorithm"
53 | )
54 | parser.add_argument(
55 | "--depth",
56 | type=int,
57 | default=10,
58 | help="Number of depth values used in the Manifold algorithm"
59 | )
60 |
--------------------------------------------------------------------------------
/scripts/check_watertightness.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Script for checking watertightness property of meshes.
3 | """
4 | import argparse
5 | import logging
6 | import os
7 | import sys
8 |
9 | import numpy as np
10 | import trimesh
11 | from tqdm import tqdm
12 | from watertight_transformer.datasets import ModelCollectionBuilder
13 |
14 |
15 | def ensure_parent_directory_exists(filepath):
16 | os.makedirs(os.path.dirname(filepath), exist_ok=True)
17 |
18 |
19 | def main(argv):
20 | parser = argparse.ArgumentParser(
21 | description="Check if generated meshes are watertight and save non-watertight model paths to a file"
22 | )
23 | parser.add_argument("dataset_directory", help="Path to the directory containing the dataset")
24 | parser.add_argument(
25 | "text_directory",
26 | help="Path to the directory that will have the list of non watertight meshes file",
27 | )
28 | parser.add_argument(
29 | "--dataset_type",
30 | default="shapenet_v1",
31 | choices=["shapenet_v1", "dynamic_faust", "freihand", "3d_future", "deforming_things_4d"],
32 | help="The type of the dataset type to be used",
33 | )
34 | parser.add_argument(
35 | "--model_tags",
36 | type=lambda x: x.split(","),
37 | default=[],
38 | help="Tags to the models to be used",
39 | )
40 | parser.add_argument(
41 | "--category_tags",
42 | type=lambda x: x.split(","),
43 | default=[],
44 | help="Category tags to the models to be used",
45 | )
46 |
47 | args = parser.parse_args(argv)
48 | # Disable trimesh's logger
49 | logging.getLogger("trimesh").setLevel(logging.ERROR)
50 |
51 | dataset = (
52 | ModelCollectionBuilder()
53 | .with_dataset(args.dataset_type)
54 | .filter_category_tags(args.category_tags)
55 | .filter_tags(args.model_tags)
56 | .build(args.dataset_directory)
57 | )
58 |
59 | count = 0
60 | with open(f"{args.text_directory}/non_watertight_list.txt", "w") as f:
61 | for sample in tqdm(dataset):
62 | # Assemble the target path and ensure the parent dir exists
63 | path_to_file = sample.path_to_watertight_mesh_file
64 |
65 | # If path does not exist we need to log that
66 | if not os.path.exists(path_to_file):
67 | print(f"File does not exist in location: {path_to_file}")
68 |
69 | try:
70 | # Load mesh using Trimesh to check watertightness
71 | tr_mesh = trimesh.load(path_to_file, process=False, force="mesh")
72 | except Exception as e:
73 | print(f"Error raised while loading file {path_to_file}")
74 | raise
75 | if not tr_mesh.is_watertight:
76 | count += 1
77 | f.write(path_to_file + "\n")
78 | if not count:
79 | print("All meshes in the relevant directory are watertight!")
80 |
81 |
82 | if __name__ == "__main__":
83 | main(sys.argv[1:])
84 |
--------------------------------------------------------------------------------
/scripts/convert_to_watertight.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Script for converting non-watertight meshes to watertight meshes. This
3 | script is intended for meshes organized in datasets."""
4 |
5 | import argparse
6 | import logging
7 | import sys
8 | from functools import partial
9 |
10 | import trimesh
11 | from tqdm.contrib.concurrent import process_map
12 | from watertight_transformer import WatertightTransformerFactory
13 | from watertight_transformer.datasets import ModelCollectionBuilder
14 | from watertight_transformer.datasets.model_collections import \
15 | BaseModel, ModelCollection
16 |
17 | from arguments import add_manifoldplus_parameters, \
18 | add_tsdf_fusion_parameters
19 | from utils import mesh_to_watertight
20 |
21 |
22 | def distribute_files(
23 | dataset: ModelCollection,
24 | wat_transformer,
25 | bbox: list = None,
26 | unit_cube: bool = False,
27 | simplify: bool = None,
28 | num_target_faces: int = None,
29 | ratio_target_faces: float = None,
30 | num_cpus: int = 1,
31 | ):
32 | # Assuming that dataset iterator contains only one instance of each path
33 | process_map(
34 | partial(
35 | ds_sample_to_watertight,
36 | wat_transformer=wat_transformer,
37 | bbox=bbox,
38 | unit_cube=unit_cube,
39 | simplify=simplify,
40 | num_target_faces=num_target_faces,
41 | ratio_target_faces=ratio_target_faces,
42 | ),
43 | dataset,
44 | max_workers=num_cpus,
45 | )
46 |
47 |
48 | def ds_sample_to_watertight(
49 | sample: BaseModel,
50 | wat_transformer,
51 | bbox: list = None,
52 | unit_cube: bool = False,
53 | simplify: bool = None,
54 | num_target_faces: int = None,
55 | ratio_target_faces: float = None,
56 | ):
57 | mesh = sample.groundtruth_mesh
58 | path_to_file = sample.path_to_watertight_mesh_file
59 | mesh_to_watertight(
60 | mesh=mesh,
61 | wat_transformer=wat_transformer,
62 | path_to_file=path_to_file,
63 | bbox=bbox,
64 | unit_cube=unit_cube,
65 | simplify=simplify,
66 | num_target_faces=num_target_faces,
67 | ratio_target_faces=ratio_target_faces,
68 | )
69 |
70 |
71 | def main(argv):
72 | parser = argparse.ArgumentParser(
73 | description="Convert non-watertight meshes to watertight"
74 | )
75 | parser.add_argument(
76 | "dataset_directory",
77 | help="Path to the directory containing the dataset"
78 | )
79 | parser.add_argument(
80 | "--dataset_type",
81 | default="shapenet_v1",
82 | choices=[
83 | "shapenet_v1",
84 | "dynamic_faust",
85 | "freihand",
86 | "3d_future",
87 | "deforming_things_4d"
88 | ],
89 | help="The type of the dataset type to be used",
90 | )
91 | parser.add_argument(
92 | "--model_tags",
93 | type=lambda x: x.split(","),
94 | default=[],
95 | help="Tags to the models to be used",
96 | )
97 | parser.add_argument(
98 | "--category_tags",
99 | type=lambda x: x.split(","),
100 | default=[],
101 | help="Category tags to the models to be used",
102 | )
103 | parser.add_argument(
104 | "--watertight_method",
105 | default="tsdf_fusion",
106 | choices=[
107 | "tsdf_fusion",
108 | "manifoldplus"
109 | ]
110 | )
111 | parser.add_argument(
112 | "--unit_cube",
113 | action="store_true",
114 | help="Normalize mesh to fit a unit cube"
115 | )
116 | parser.add_argument(
117 | "--bbox",
118 | type=lambda x: list(map(float, x.split(","))),
119 | default=None,
120 | help=("Bounding box to be used for scaling. "
121 | "By default we use the unit cube"),
122 | )
123 | parser.add_argument(
124 | "--simplify",
125 | action="store_true",
126 | help="Simplify the watertight mesh"
127 | )
128 | parser.add_argument(
129 | "--num_target_faces",
130 | type=int,
131 | default=None,
132 | help="Max number of faces in the simplified mesh",
133 | )
134 | parser.add_argument(
135 | "--ratio_target_faces",
136 | type=float,
137 | default=None,
138 | help="Ratio of target faces with regards to input mesh faces",
139 | )
140 | parser.add_argument(
141 | "--num_cpus",
142 | type=int,
143 | default=1,
144 | help="Number of processes to be used for the multiprocessing setup"
145 | )
146 |
147 | add_tsdf_fusion_parameters(parser)
148 | add_manifoldplus_parameters(parser)
149 | args = parser.parse_args(argv)
150 | # Disable trimesh's logger
151 | logging.getLogger("trimesh").setLevel(logging.ERROR)
152 |
153 | dataset = (
154 | ModelCollectionBuilder()
155 | .with_dataset(args.dataset_type)
156 | .filter_category_tags(args.category_tags)
157 | .filter_tags(args.model_tags)
158 | .build(args.dataset_directory)
159 | )
160 |
161 | wat_transformer = WatertightTransformerFactory(
162 | args.watertight_method,
163 | image_height=args.image_size[0],
164 | image_width=args.image_size[1],
165 | focal_length_x=args.focal_point[0],
166 | focal_length_y=args.focal_point[1],
167 | principal_point_x=args.principal_point[0],
168 | principal_point_y=args.principal_point[1],
169 | resolution=args.resolution,
170 | truncation_factor=args.truncation_factor,
171 | n_views=args.n_views,
172 | depth_offset_factor=args.depth_offset_factor,
173 | manifoldplus_script=args.manifoldplus_script,
174 | depth=args.depth,
175 | )
176 |
177 | distribute_files(
178 | dataset=dataset,
179 | wat_transformer=wat_transformer,
180 | bbox=args.bbox,
181 | unit_cube=args.unit_cube,
182 | simplify=args.simplify,
183 | num_target_faces=args.num_target_faces,
184 | ratio_target_faces=args.ratio_target_faces,
185 | num_cpus=args.num_cpus,
186 | )
187 |
188 |
189 | if __name__ == "__main__":
190 | main(sys.argv[1:])
191 |
--------------------------------------------------------------------------------
/scripts/make_mesh_watertight.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import argparse
3 | import logging
4 | import os
5 | import sys
6 | from functools import partial
7 |
8 | import trimesh
9 | from simple_3dviz import Mesh
10 | from tqdm.contrib.concurrent import process_map
11 | from watertight_transformer import WatertightTransformerFactory
12 |
13 | from arguments import add_manifoldplus_parameters, add_tsdf_fusion_parameters
14 | from utils import ensure_parent_directory_exists, mesh_to_watertight
15 |
16 |
17 | def distribute_files(
18 | mesh_paths: list,
19 | output_folder_path: str,
20 | wat_transformer,
21 | bbox: list = None,
22 | unit_cube: bool = False,
23 | simplify: bool = None,
24 | num_target_faces: int = None,
25 | ratio_target_faces: float = None,
26 | num_cpus: int = 1,
27 | ):
28 | # Assuming that dataset iterator contains only one instance of each path
29 | process_map(
30 | partial(
31 | mesh_path_to_watertight,
32 | output_folder_path=output_folder_path,
33 | wat_transformer=wat_transformer,
34 | bbox=bbox,
35 | unit_cube=unit_cube,
36 | simplify=simplify,
37 | num_target_faces=num_target_faces,
38 | ratio_target_faces=ratio_target_faces,
39 | ),
40 | mesh_paths,
41 | max_workers=num_cpus,
42 | )
43 |
44 |
45 | def mesh_path_to_watertight(
46 | mesh_path: str,
47 | output_folder_path: str,
48 | wat_transformer,
49 | bbox: list = None,
50 | unit_cube: bool = False,
51 | simplify: bool = None,
52 | num_target_faces: int = None,
53 | ratio_target_faces: float = None,
54 | ):
55 | file_name = mesh_path.split("/")[-1].split(".")[0]
56 | # path_to_file = os.path.join(output_folder_path, f"{file_name}.obj")
57 | path_to_file = os.path.join(output_folder_path, "model_watertight.obj")
58 | raw_mesh = Mesh.from_file(mesh_path)
59 | mesh_to_watertight(
60 | mesh=raw_mesh,
61 | wat_transformer=wat_transformer,
62 | path_to_file=path_to_file,
63 | bbox=bbox,
64 | unit_cube=unit_cube,
65 | simplify=simplify,
66 | num_target_faces=num_target_faces,
67 | ratio_target_faces=ratio_target_faces,
68 | )
69 |
70 |
71 | def main(argv):
72 | parser = argparse.ArgumentParser(
73 | description="Convert non-watertight meshes to watertight"
74 | )
75 | parser.add_argument(
76 | "path_to_meshes",
77 | help="Path to folder containing the mesh/meshes to be converted"
78 | )
79 | parser.add_argument(
80 | "path_to_output_directory",
81 | help="Path to save the watertight mesh")
82 | parser.add_argument(
83 | "--watertight_method",
84 | default="tsdf_fusion",
85 | choices=["tsdf_fusion", "manifoldplus"]
86 | )
87 | parser.add_argument(
88 | "--unit_cube",
89 | action="store_true",
90 | help="Normalize mesh to fit a unit cube"
91 | )
92 | parser.add_argument(
93 | "--bbox",
94 | type=lambda x: list(map(float, x.split(","))),
95 | default=None,
96 | help=("Bounding box to be used for scaling. "
97 | "By default we use the unit cube")
98 | )
99 | parser.add_argument(
100 | "--simplify",
101 | action="store_true",
102 | help="Simplify the watertight mesh"
103 | )
104 | parser.add_argument(
105 | "--num_target_faces",
106 | type=int,
107 | default=None,
108 | help="Max number of faces in the simplified mesh",
109 | )
110 | parser.add_argument(
111 | "--ratio_target_faces",
112 | type=float,
113 | default=None,
114 | help="Ratio of target faces with regards to input mesh faces",
115 | )
116 | parser.add_argument(
117 | "--num_cpus",
118 | type=int,
119 | default=1,
120 | help="Number of processes to be used for the multiprocessing setup",
121 | )
122 |
123 | add_tsdf_fusion_parameters(parser)
124 | add_manifoldplus_parameters(parser)
125 | args = parser.parse_args(argv)
126 | # Disable trimesh's logger
127 | logging.getLogger("trimesh").setLevel(logging.ERROR)
128 |
129 | if os.path.isdir(args.path_to_meshes):
130 | path_to_meshes = [
131 | os.path.join(args.path_to_meshes, mi)
132 | for mi in os.listdir(args.path_to_meshes)
133 | if mi.endswith(".obj") or mi.endswith(".off")
134 | ]
135 | else:
136 | path_to_meshes = [args.path_to_meshes]
137 |
138 | # Check optimistically if the file already exists
139 | ensure_parent_directory_exists(args.path_to_output_directory)
140 |
141 | wat_transformer = WatertightTransformerFactory(
142 | args.watertight_method,
143 | image_height=args.image_size[0],
144 | image_width=args.image_size[1],
145 | focal_length_x=args.focal_point[0],
146 | focal_length_y=args.focal_point[1],
147 | principal_point_x=args.principal_point[0],
148 | principal_point_y=args.principal_point[1],
149 | resolution=args.resolution,
150 | truncation_factor=args.truncation_factor,
151 | n_views=args.n_views,
152 | depth_offset_factor=args.depth_offset_factor,
153 | manifoldplus_script=args.manifoldplus_script,
154 | depth=args.depth,
155 | )
156 |
157 | distribute_files(
158 | mesh_paths=path_to_meshes,
159 | output_folder_path=args.path_to_output_directory,
160 | wat_transformer=wat_transformer,
161 | bbox=args.bbox,
162 | unit_cube=args.unit_cube,
163 | simplify=args.simplify,
164 | num_target_faces=args.num_target_faces,
165 | ratio_target_faces=args.ratio_target_faces,
166 | num_cpus=args.num_cpus,
167 | )
168 |
169 |
170 | if __name__ == "__main__":
171 | main(sys.argv[1:])
172 |
--------------------------------------------------------------------------------
/scripts/simplification.mlx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/scripts/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import numpy as np
4 | import pymeshlab
5 | import trimesh
6 | from watertight_transformer.base import WatertightTransformerFactory
7 | from watertight_transformer.datasets.model_collections import Mesh
8 |
9 |
10 | def ensure_parent_directory_exists(filepath):
11 | os.makedirs(os.path.dirname(filepath), exist_ok=True)
12 |
13 |
14 | def mesh_to_watertight(
15 | mesh: Mesh,
16 | wat_transformer: WatertightTransformerFactory,
17 | path_to_file: str,
18 | bbox: list = None,
19 | unit_cube: bool = False,
20 | simplify: bool = False,
21 | num_target_faces: int = None,
22 | ratio_target_faces: float = None,
23 | ):
24 | # Check optimistically if the file already exists
25 | if os.path.exists(path_to_file):
26 | return
27 | ensure_parent_directory_exists(path_to_file)
28 | # Extract the file type from the output file
29 | file_type = path_to_file.split(".")[-1]
30 | if file_type not in ["off", "obj"]:
31 | raise Exception(f"The {file_type} is not a valid mesh extension")
32 |
33 | if bbox is not None:
34 | # Scale the mesh to range specified from the input bounding box
35 | bbox_min = np.array(bbox[:3])
36 | bbox_max = np.array(bbox[3:])
37 | dims = bbox_max - bbox_min
38 | mesh._vertices -= dims / 2 + bbox_min
39 | mesh._vertices /= dims.max()
40 | else:
41 | if unit_cube:
42 | # Scale the mesh to range [-0.5,0.5]^3
43 | # This is needed for TSDF Fusion!
44 | mesh.to_unit_cube()
45 | # Extract the points and the faces from the mesh
46 | points, faces = mesh.to_points_and_faces()
47 |
48 | tr_mesh = trimesh.Trimesh(vertices=points, faces=faces)
49 | # Check if the mesh is indeed non-watertight before making the
50 | # conversion
51 | #if tr_mesh.is_watertight:
52 | # # print(f"Mesh file: {path_to_file} is watertight...")
53 | # # tr_mesh.export(path_to_file, file_type=file_type)
54 | #else:
55 | # Make the mesh watertight with TSDF Fusion or ManifoldPlus
56 | wat_transformer.to_watertight(
57 | tr_mesh, path_to_file, file_type=file_type
58 | )
59 |
60 | if simplify:
61 | if num_target_faces:
62 | num_faces = num_target_faces
63 | else:
64 | num_faces = int(ratio_target_faces * len(faces))
65 | # Call the meshlabserver to simplify the mesh
66 | ms = pymeshlab.MeshSet()
67 | ms.load_new_mesh(path_to_file)
68 | ms.meshing_decimation_quadric_edge_collapse(
69 | targetfacenum=num_faces,
70 | qualitythr=0.5,
71 | preservenormal=True,
72 | planarquadric=True,
73 | preservetopology=True,
74 | autoclean=False, # very important for watertightness preservation
75 | )
76 | ms.save_current_mesh(path_to_file)
77 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Setup watertight_transformer."""
3 |
4 | from itertools import dropwhile
5 | from setuptools import find_packages, setup
6 | from os import path
7 |
8 | import numpy as np
9 |
10 | from Cython.Build import cythonize
11 | from Cython.Distutils import build_ext
12 | from distutils.extension import Extension
13 |
14 |
15 | def collect_docstring(lines):
16 | """Return document docstring if it exists"""
17 | lines = dropwhile(lambda x: not x.startswith('"""'), lines)
18 | doc = ""
19 | for line in lines:
20 | doc += line
21 | if doc.endswith('"""\n'):
22 | break
23 |
24 | return doc[3:-4].replace("\r", "").replace("\n", " ")
25 |
26 |
27 | def collect_metadata():
28 | meta = {}
29 | with open(path.join("watertight_transformer", "__init__.py")) as f:
30 | lines = iter(f)
31 | meta["description"] = collect_docstring(lines)
32 | for line in lines:
33 | if line.startswith("__"):
34 | key, value = map(lambda x: x.strip(), line.split("="))
35 | meta[key[2:-2]] = value[1:-1]
36 |
37 | return meta
38 |
39 |
40 | def get_extensions():
41 | extra_compile_args = [
42 | "-ffast-math",
43 | "-msse",
44 | "-msse2",
45 | "-msse3",
46 | "-msse4.2",
47 | "-O4",
48 | "-fopenmp"
49 | ]
50 | extra_link_args = [
51 | "-lGLEW",
52 | "-lglut",
53 | "-lGL",
54 | "-lGLU",
55 | "-fopenmp"
56 | ]
57 | return cythonize([
58 | Extension(
59 | "watertight_transformer.external.libmesh.triangle_hash",
60 | sources=["watertight_transformer/external/libmesh/triangle_hash.pyx"],
61 | include_dirs=[np.get_include()],
62 | libraries=["m"] # Unix-like specific
63 | ),
64 | Extension(
65 | "watertight_transformer.external.librender.pyrender",
66 | sources=[
67 | "watertight_transformer/external/librender/pyrender.pyx",
68 | "watertight_transformer/external/librender/offscreen.cpp"
69 | ],
70 | language="c++",
71 | include_dirs=[np.get_include()],
72 | extra_compile_args=extra_compile_args,
73 | extra_link_args=extra_link_args,
74 | libraries=["m"] # Unix-like specific
75 | ),
76 | Extension(
77 | "watertight_transformer.external.libmcubes.mcubes",
78 | sources=[
79 | "watertight_transformer/external/libmcubes/mcubes.pyx",
80 | "watertight_transformer/external/libmcubes/pywrapper.cpp",
81 | "watertight_transformer/external/libmcubes/marchingcubes.cpp"
82 | ],
83 | language="c++",
84 | include_dirs=[np.get_include()],
85 | extra_compile_args=["-std=c++11"],
86 | libraries=["m"] # Unix-like specific
87 | ),
88 | Extension(
89 | "watertight_transformer.external.libfusioncpu.cyfusion",
90 | sources=[
91 | "watertight_transformer/external/libfusioncpu/cyfusion.pyx",
92 | "watertight_transformer/external/libfusioncpu/fusion.cpp"
93 | ],
94 | language="c++",
95 | libraries=["m"],
96 | include_dirs=[np.get_include()],
97 | extra_compile_args=[
98 | "-fopenmp", "-ffast-math", "-msse", "-msse2", "-msse3", "-msse4.2"
99 | ],
100 | extra_link_args=["-fopenmp"]
101 | ),
102 | #Extension(
103 | # "watertight_transformer.external.libfusiongpu.cyfusion",
104 | # sources=[
105 | # "watertight_transformer/external/libfusiongpu/cyfusion.pyx",
106 | # ],
107 | # language="c++",
108 | # library_dirs=["watertight_transformer/external/libfusiongpu/build/"],
109 | # libraries=["m", "fusion_gpu"],
110 | # include_dirs=[np.get_include()],
111 | # extra_compile_args=[
112 | # "-ffast-math", "-msse", "-msse2", "-msse3", "-msse4.2"
113 | # ]
114 | #),
115 | ])
116 |
117 |
118 | def get_install_requirements():
119 | return [
120 | "numpy",
121 | "scipy",
122 | "cython",
123 | "pycollada",
124 | "Pillow",
125 | "trimesh",
126 | "tqdm",
127 | "h5py",
128 | "pymeshlab"
129 | ]
130 |
131 | def setup_package():
132 | with open("README.md") as f:
133 | long_description = f.read()
134 | meta = collect_metadata()
135 | setup(
136 | name="watertight_transformer",
137 | cmdclass={"build_ext": build_ext},
138 | version=meta["version"],
139 | description=meta["description"],
140 | long_description=long_description,
141 | maintainer=meta["maintainer"],
142 | maintainer_email=meta["email"],
143 | url=meta["url"],
144 | license=meta["license"],
145 | classifiers=[
146 | "Intended Audience :: Science/Research",
147 | "Intended Audience :: Developers",
148 | "License :: OSI Approved :: MIT License",
149 | "Topic :: Scientific/Engineering",
150 | "Programming Language :: Python",
151 | "Programming Language :: Python :: 3",
152 | "Programming Language :: Python :: 3.8",
153 | ],
154 | packages=find_packages(exclude=["scripts"]),
155 | install_requires=get_install_requirements(),
156 | ext_modules=get_extensions()
157 | )
158 |
159 |
160 | if __name__ == "__main__":
161 | setup_package()
162 |
--------------------------------------------------------------------------------
/watertight_transformer/__init__.py:
--------------------------------------------------------------------------------
1 | """Convert meshes to watertight meshes using TSDF fusion and ManifoldPlus."""
2 |
3 | __author__ = "Despoina Paschalidou"
4 | __license__ = "MIT"
5 | __maintainer__ = "Despoina Paschalidou"
6 | __email__ = "paschald@stanford.edu"
7 | __url__ = "https://paschalidoud.github.io/"
8 | __version__ = "0.1"
9 |
10 | from .base import WatertightTransformerFactory
11 |
--------------------------------------------------------------------------------
/watertight_transformer/base.py:
--------------------------------------------------------------------------------
1 | from tempfile import NamedTemporaryFile
2 |
3 | from .manifoldplus import ManifoldPlus
4 | from .tsdf_fusion import TSDFFusion
5 |
6 |
7 | class WatertightTransformerFactory:
8 | """
9 | Arguments:
10 | ----------
11 | image_height: Image height of depth map generated during TSDFFusion
12 | image_width: Image width of depth map generated during TSDFFusion
13 | focal_length_x: The focal length along the x-axis for TSDFFusion
14 | focal_length_y: The focal length along the y-axis for TSDFFusion
15 | principal_point_x: The principal point along the x-axis for TSDFFusion
16 | principal_point_y: The principal point along the y-axis for TSDFFusion
17 | resolution: The resoluion for the TSDFFusion
18 | truncation_factor: The truncation_factor for the TSDFFusion is derived as
19 | truncation_factor*voxel_size
20 | depth_offset_factor: The depth maps are offsetted using
21 | depth_offset_factor*voxel_size in TSDFFusion
22 | n_views: The number of views used in TSDFFusion
23 | manifold_plus_script: Path to the binary file to be used to perform the
24 | Manifold algorithm
25 | depth: Number of depth values used in the Manifold algorithm
26 | """
27 | def __init__(
28 | self,
29 | name,
30 | image_height=640,
31 | image_width=640,
32 | focal_length_x=640,
33 | focal_length_y=640,
34 | principal_point_x=320,
35 | principal_point_y=320,
36 | resolution=256,
37 | truncation_factor=15,
38 | n_views=100,
39 | depth_offset_factor=1.5,
40 | manifoldplus_script=None,
41 | depth=10,
42 | ):
43 | self.name = name
44 | if self.name == "manifoldplus":
45 | # Make sure that the correct arguments are provided
46 | if manifoldplus_script is None:
47 | raise Exception(
48 | "Cannot run ManifoldPlus without specifying a script"
49 | )
50 | self.wat_transformer = ManifoldPlus(
51 | manifoldplus_script=manifoldplus_script,
52 | depth=depth
53 | )
54 | elif self.name == "tsdf_fusion":
55 | self.wat_transformer = TSDFFusion(
56 | image_height=image_height,
57 | image_width=image_width,
58 | focal_length_x=focal_length_y,
59 | focal_length_y=focal_length_y,
60 | principal_point_x=principal_point_x,
61 | principal_point_y=principal_point_y,
62 | resolution=resolution,
63 | truncation_factor=truncation_factor,
64 | n_views=n_views,
65 | depth_offset_factor=depth_offset_factor
66 | )
67 | else:
68 | raise NotImplementedError()
69 |
70 |
71 | def to_watertight(self, mesh, path_to_watertight, file_type="off"):
72 | if self.name == "manifoldplus":
73 | # Create a temporary file and store the mesh
74 | path_to_mesh = NamedTemporaryFile().name + file_type
75 | mesh.export(path_to_mesh, file_type=file_type)
76 | self.wat_transformer.to_watertight(
77 | path_to_mesh, path_to_watertight, file_type
78 | )
79 | elif self.name == "tsdf_fusion":
80 | self.wat_transformer.to_watertight(
81 | mesh, path_to_watertight, file_type
82 | )
83 | else:
84 | raise NotImplementedError()
85 |
--------------------------------------------------------------------------------
/watertight_transformer/datasets/__init__.py:
--------------------------------------------------------------------------------
1 | from .model_collections import ModelCollectionBuilder
2 |
--------------------------------------------------------------------------------
/watertight_transformer/datasets/model_collections.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | try:
3 | from functools import lru_cache
4 | except ImportError:
5 | from backports.functools_lru_cache import lru_cache
6 | from collections import OrderedDict
7 | import os
8 | from PIL import Image
9 |
10 | from simple_3dviz import Mesh
11 |
12 |
13 | class BaseModel(object):
14 | """BaseModel class is wrapper for all models, independent of dataset. Every
15 | model has a unique model_tag, mesh_file and Mesh object. Optionally, it
16 | can also have a tsdf file.
17 | """
18 |
19 | def __init__(self, tag):
20 | self._tag = tag
21 | # Initialize the contents of this instance to empty so that they can be
22 | # lazy loaded
23 | self._gt_mesh = None
24 | self._images = []
25 | self._image_paths = None
26 |
27 | @property
28 | def tag(self):
29 | return self._tag
30 |
31 | @property
32 | def path_to_mesh_file(self):
33 | raise NotImplementedError()
34 |
35 | @property
36 | def path_to_watertight_mesh_file(self):
37 | raise NotImplementedError()
38 |
39 | @property
40 | def images_dir(self):
41 | raise NotImplementedError()
42 |
43 | @property
44 | def groundtruth_mesh(self):
45 | if self._gt_mesh is None:
46 | self._gt_mesh = Mesh.from_file(self.path_to_mesh_file)
47 | return self._gt_mesh
48 |
49 | @groundtruth_mesh.setter
50 | def groundtruth_mesh(self, mesh):
51 | if self._gt_mesh is not None:
52 | raise RuntimeError("Trying to overwrite a mesh")
53 | self._gt_mesh = mesh
54 |
55 | @property
56 | def image_paths(self):
57 | if self._image_paths is None:
58 | self._image_paths = [
59 | os.path.join(self.images_dir, p)
60 | for p in sorted(os.listdir(self.images_dir))
61 | if p.endswith(".jpg") or p.endswith(".png")
62 | ]
63 | return self._image_paths
64 |
65 | def get_image(self, idx):
66 | return np.array(Image.open(self.image_paths[idx]).convert("RGB"))
67 |
68 |
69 | class ModelCollection(object):
70 | def __len__(self):
71 | raise NotImplementedError()
72 |
73 | def _get_model(self, i):
74 | raise NotImplementedError()
75 |
76 | def __getitem__(self, i):
77 | if i >= len(self):
78 | raise IndexError()
79 | return self._get_model(i)
80 |
81 |
82 | class ModelSubset(ModelCollection):
83 | def __init__(self, collection, subset):
84 | self._collection = collection
85 | self._subset = subset
86 |
87 | def __len__(self):
88 | return len(self._subset)
89 |
90 | def _get_sample(self, i):
91 | return self._collection[self._subset[i]]
92 |
93 | def __getitem__(self, i):
94 | if i >= len(self):
95 | raise IndexError()
96 | return self._get_sample(i)
97 |
98 |
99 | class TagSubset(ModelSubset):
100 | def __init__(self, collection, tags):
101 | tags = set(tags)
102 | subset = [i for (i, m) in enumerate(collection) if m.tag in tags]
103 | super(TagSubset, self).__init__(collection, subset)
104 |
105 |
106 | class RandomSubset(ModelSubset):
107 | def __init__(self, collection, percentage):
108 | N = len(collection)
109 | subset = np.random.choice(N, int(N*percentage)).tolist()
110 | super(RandomSubset, self).__init__(collection, subset)
111 |
112 |
113 | class CategorySubset(ModelSubset):
114 | def __init__(self, collection, category_tags):
115 | category_tags = set(category_tags)
116 | subset = [
117 | i
118 | for (i, m) in enumerate(collection)
119 | if m.category in category_tags
120 | ]
121 | super(CategorySubset, self).__init__(collection, subset)
122 |
123 |
124 | class DynamicFaust(ModelCollection):
125 | class Model(BaseModel):
126 | def __init__(self, base_dir, tag):
127 | super().__init__(tag)
128 | self._base_dir = base_dir
129 | self._category, self._sequence = tag.split(":")
130 |
131 | @property
132 | def category(self):
133 | return self._category
134 |
135 | @property
136 | def path_to_mesh_file(self):
137 | return os.path.join(self._base_dir, self._category,
138 | "mesh_seq", self._sequence+".obj")
139 | @property
140 | def path_to_watertight_mesh_file(self):
141 | return os.path.join(self._base_dir, self._category,
142 | "watertight_mesh_seq", self._sequence+".obj")
143 | @property
144 | def image_paths(self):
145 | return [os.path.join(self._base_dir, self._category,
146 | self._renderings_folder,
147 | "{}.png".format(self._sequence))]
148 |
149 | def __init__(self, base_dir):
150 | self._base_dir = base_dir
151 | self._paths = sorted([
152 | d
153 | for d in os.listdir(self._base_dir)
154 | if os.path.isdir(os.path.join(self._base_dir, d))
155 | ])
156 |
157 | # Note that we filter out the first 20 meshes from the sequence to
158 | # "discard" the neutral pose that is used for calibration purposes.
159 | self._tags = sorted([
160 | "{}:{}".format(d, l[:-4]) for d in self._paths
161 | for l in sorted(os.listdir(os.path.join(self._base_dir, d, mesh_folder)))[20:]
162 | if l.endswith(".obj")
163 | ])
164 |
165 | print("Found {} Dynamic Faust models".format(len(self)))
166 |
167 | def __len__(self):
168 | return len(self._tags)
169 |
170 | def _get_model(self, i):
171 | return self.Model(self._base_dir, self._tags[i])
172 |
173 |
174 | class FreiHand(ModelCollection):
175 | class Model(BaseModel):
176 | def __init__(self, base_dir, tag):
177 | super().__init__(tag)
178 | self._base_dir = base_dir
179 |
180 | @property
181 | def category(self):
182 | return ""
183 |
184 | @property
185 | def path_to_mesh_file(self):
186 | return os.path.join(self._base_dir, self.tag + ".obj")
187 |
188 | @property
189 | def path_to_watertight_mesh_file(self):
190 | return os.path.join(self._base_dir, self.tag + "_watertight.obj")
191 |
192 | @property
193 | def image_paths(self):
194 | return [os.path.join(self._base_dir, self.tag + ".png")]
195 |
196 | def __init__(self, base_dir):
197 | self._base_dir = base_dir
198 | self._tags = sorted([
199 | f[:-4]
200 | for f in os.listdir(self._base_dir)
201 | if f.endswith(".png")
202 | ])
203 |
204 | def __len__(self):
205 | return len(self._tags)
206 |
207 | def _get_model(self, i):
208 | return self.Model(self._base_dir, self._tags[i])
209 |
210 |
211 | class ThreedFuture(ModelCollection):
212 | class Model(BaseModel):
213 | def __init__(self, base_dir, tag):
214 | super().__init__(tag)
215 | self._base_dir = base_dir
216 |
217 | @property
218 | def path_to_mesh_file(self):
219 | return os.path.join(self._base_dir, self._tag, "raw_model.obj")
220 |
221 | @property
222 | def path_to_watertight_mesh_file(self):
223 | return os.path.join(self._base_dir, self._tag,
224 | "model_watertight.off")
225 |
226 | def __init__(self, base_dir):
227 | self._base_dir = base_dir
228 | self._tags = sorted([
229 | fi for fi in os.listdir(self._base_dir)
230 | if os.path.isdir(os.path.join(self._base_dir, fi))
231 | ])
232 |
233 | print("Found {} 3D-Future models".format(len(self)))
234 |
235 | def __len__(self):
236 | return len(self._tags)
237 |
238 | def _get_model(self, i):
239 | return self.Model(self._base_dir, self._tags[i])
240 |
241 |
242 | class MultiModelsShapeNetV1(ModelCollection):
243 | class Model(BaseModel):
244 | def __init__(self, base_dir, tag):
245 | super().__init__(tag)
246 | self._base_dir = base_dir
247 | self._category, self._model = tag.split(":")
248 |
249 | @property
250 | def category(self):
251 | return self._category
252 |
253 | @property
254 | def path_to_mesh_file(self):
255 | return os.path.join(self._base_dir, self._category, self._model,
256 | "model.obj")
257 |
258 | @property
259 | def path_to_watertight_mesh_file(self):
260 | return os.path.join(self._base_dir, self._category, self._model,
261 | "model_watertight.off")
262 |
263 | @property
264 | def images_dir(self):
265 | return os.path.join(self._base_dir, self._category, self._model,
266 | "img_choy2016")
267 |
268 | def __init__(self, base_dir):
269 | self._base_dir = base_dir
270 | self._models = sorted([
271 | d
272 | for d in os.listdir(self._base_dir)
273 | if os.path.isdir(os.path.join(self._base_dir, d))
274 | ])
275 |
276 | self._tags = sorted([
277 | "{}:{}".format(d, l) for d in self._models
278 | for l in os.listdir(os.path.join(self._base_dir, d))
279 | if os.path.isdir(os.path.join(self._base_dir, d, l))
280 | ])
281 |
282 | print("Found {} MultiModelsShapeNetV1 models".format(len(self)))
283 |
284 | def __len__(self):
285 | return len(self._tags)
286 |
287 | def _get_model(self, i):
288 | return self.Model(self._base_dir, self._tags[i])
289 |
290 |
291 | class DeformingThings4D(ModelCollection):
292 | class Model(BaseModel):
293 | def __init__(self, base_dir, tag):
294 | super().__init__(tag)
295 | self._base_dir = base_dir
296 | self._category, self._sequence = tag.split(":")
297 |
298 | @property
299 | def category(self):
300 | return self._category
301 |
302 | @property
303 | def path_to_mesh_file(self):
304 | return os.path.join(
305 | self._base_dir,
306 | self._category,
307 | "mesh_seq",
308 | self._sequence + ".obj"
309 | )
310 |
311 | @property
312 | def path_to_watertight_mesh_file(self):
313 | return os.path.join(
314 | self._base_dir,
315 | self._category,
316 | "watertight_mesh_seq",
317 | f"{self._sequence}.obj"
318 | )
319 |
320 | def __init__(self, base_dir):
321 | self._base_dir = base_dir
322 | self._paths = sorted(
323 | [
324 | d
325 | for d in os.listdir(self._base_dir)
326 | if os.path.isdir(os.path.join(self._base_dir, d))
327 | ]
328 | )
329 |
330 | self._tags = sorted(
331 | [
332 | "{}:{}".format(d, l[:-4])
333 | for d in self._paths
334 | for l in sorted(os.listdir(os.path.join(self._base_dir, d, "mesh_seq")))
335 | if l.endswith(".obj")
336 | ]
337 | )
338 |
339 | print("Found {} DeformingThings4D models".format(len(self)))
340 |
341 | def __len__(self):
342 | return len(self._tags)
343 |
344 | def _get_model(self, i):
345 | return self.Model(self._base_dir, self._tags[i])
346 |
347 |
348 | class MeshCache(ModelCollection):
349 | """Cache the meshes from a collection and give them to the model before
350 | returning it."""
351 | def __init__(self, collection):
352 | self._collection = collection
353 | self._meshes = [None]*len(collection)
354 |
355 | def __len__(self):
356 | return len(self._collection)
357 |
358 | def _get_model(self, i):
359 | model = self._collection._get_model(i)
360 | if self._meshes[i] is not None:
361 | model.groundtruth_mesh = self._meshes[i]
362 | else:
363 | self._meshes[i] = model.groundtruth_mesh
364 |
365 | return model
366 |
367 |
368 | class LRUCache(ModelCollection):
369 | def __init__(self, collection, n=2000):
370 | self._collection = collection
371 | self._cache = OrderedDict([])
372 | self._maxsize = n
373 |
374 | def __len__(self):
375 | return len(self._collection)
376 |
377 | def _get_model(self, i):
378 | m = None
379 | if i in self._cache:
380 | m = self._cache.pop(i)
381 | else:
382 | m = self._collection._get_model(i)
383 | if len(self._cache) > self._maxsize:
384 | self._cache.popitem()
385 | self._cache[i] = m
386 | return m
387 |
388 |
389 | def model_factory(dataset_type):
390 | return {
391 | "dynamic_faust": DynamicFaust,
392 | "shapenet_v1": MultiModelsShapeNetV1,
393 | "freihand": FreiHand,
394 | "3d_future": ThreedFuture,
395 | "deforming_things_4d": DeformingThings4D,
396 | }[dataset_type]
397 |
398 |
399 | class ModelCollectionBuilder(object):
400 | def __init__(self):
401 | self._dataset_class = None
402 | self._cache_meshes = False
403 | self._lru_cache = 0
404 | self._tags = []
405 | self._category_tags = []
406 | self._percentage = 1.0
407 |
408 | def with_dataset(self, dataset_type):
409 | self._dataset_class = model_factory(dataset_type)
410 | return self
411 |
412 | def with_cache_meshes(self):
413 | self._cache_meshes = True
414 | return self
415 |
416 | def without_cache_meshes(self):
417 | self._cache_meshes = False
418 | return self
419 |
420 | def lru_cache(self, n=2000):
421 | self._lru_cache = n
422 | return self
423 |
424 | def filter_tags(self, tags):
425 | self._tags = tags
426 | return self
427 |
428 | def filter_category_tags(self, tags):
429 | self._category_tags = tags
430 | return self
431 |
432 | def random_subset(self, percentage):
433 | self._percentage = percentage
434 | return self
435 |
436 | def build(self, base_dir):
437 | dataset = self._dataset_class(base_dir)
438 | if self._cache_meshes:
439 | dataset = MeshCache(dataset)
440 | if self._lru_cache > 0:
441 | dataset = LRUCache(dataset, self._lru_cache)
442 | if len(self._tags) > 0:
443 | prev_len = len(dataset)
444 | dataset = TagSubset(dataset, self._tags)
445 | print("Keep {}/{} based on tags".format(len(dataset), prev_len))
446 | if len(self._category_tags) > 0:
447 | prev_len = len(dataset)
448 | dataset = CategorySubset(dataset, self._category_tags)
449 | print("Keep {}/{} based on category tags".format(
450 | len(dataset), prev_len)
451 | )
452 | if self._percentage < 1.0:
453 | dataset = RandomSubset(dataset, self._percentage)
454 |
455 | return dataset
456 |
--------------------------------------------------------------------------------
/watertight_transformer/external/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paschalidoud/mesh_fusion_simple/aaefe7a0acf8f8f6f5ac611e0636f863e0154fd2/watertight_transformer/external/__init__.py
--------------------------------------------------------------------------------
/watertight_transformer/external/libfusioncpu/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2017, Gernot
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libfusioncpu/README.md:
--------------------------------------------------------------------------------
1 | # PyFusion
2 |
3 | PyFusion is a Python framework for volumetric depth fusion.
4 | It contains simple occupancy and TSDF fusion methods that can be executed on a CPU as well as on a GPU.
5 |
6 | To use the code, first compile the native code via
7 |
8 | ```bash
9 | cd build
10 | cmake ..
11 | make
12 | ```
13 | Afterwards you can compile the Cython code via
14 |
15 | ```bash
16 | python setup.py build_ext --inplace
17 | ```
18 |
19 | You can then use the fusion functions
20 |
21 | ```python
22 | import pyfusion
23 |
24 | # create a views object
25 | # depthmaps: a NxHxW numpy float tensor of N depthmaps, invalid depth values are marked by negative numbers
26 | # Ks: the camera intric matrices, Nx3x3 float tensor
27 | # Rs: the camera rotation matrices, Nx3x3 float tensor
28 | # Ts: the camera translation vectors, Nx3 float tensor
29 | views = pyfusion.PyViews(depthmaps, Ks,Rs,Ts)
30 |
31 | # afterwards you can fuse the depth maps for example by
32 | # depth,height,width: number of voxels in each dimension
33 | # truncation: TSDF truncation value
34 | tsdf = pyfusion.tsdf_gpu(views, depth,height,width, vx_size, truncation, False)
35 |
36 | # the same code can also be run on the CPU
37 | tsdf = pyfusion.tsdf_cpu(views, depth,height,width, vx_size, truncation, False, n_threads=8)
38 | ```
39 |
40 | Make sure `pyfusion` is in your `$PYTHONPATH`.
41 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libfusioncpu/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paschalidoud/mesh_fusion_simple/aaefe7a0acf8f8f6f5ac611e0636f863e0154fd2/watertight_transformer/external/libfusioncpu/__init__.py
--------------------------------------------------------------------------------
/watertight_transformer/external/libfusioncpu/cyfusion.pyx:
--------------------------------------------------------------------------------
1 | cimport cython
2 | import numpy as np
3 | cimport numpy as np
4 |
5 | from libc.stdlib cimport free, malloc
6 | from libcpp cimport bool
7 | from cpython cimport PyObject, Py_INCREF
8 |
9 | CREATE_INIT = True # workaround, so cython builds a init function
10 |
11 | np.import_array()
12 |
13 |
14 |
15 | cdef extern from "fusion.h":
16 | cdef cppclass Views:
17 | Views()
18 | int n_views_;
19 | float* depthmaps_;
20 | int rows_;
21 | int cols_;
22 | float* Ks_;
23 | float* Rs_;
24 | float* Ts_;
25 |
26 | cdef cppclass Volume:
27 | Volume()
28 | int channels_;
29 | int depth_;
30 | int height_;
31 | int width_;
32 | float* data_;
33 |
34 |
35 | void fusion_projectionmask_cpu(const Views& views, float vx_size, bool unknown_is_free, int n_threads, Volume& vol);
36 | void fusion_occupancy_cpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, int n_threads, Volume& vol);
37 | void fusion_tsdfmask_cpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, int n_threads, Volume& vol);
38 | void fusion_tsdf_cpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, int n_threads, Volume& vol);
39 | void fusion_tsdf_hist_cpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, float* bin_centers, int n_bins, bool unobserved_is_occupied, int n_threads, Volume& vol);
40 |
41 | cdef class PyViews:
42 | cdef Views views
43 | # need to keep reference, otherwise it could get garbage collected
44 | cdef float[:,:,::1] depthmaps_
45 | cdef float[:,:,::1] Ks_
46 | cdef float[:,:,::1] Rs_
47 | cdef float[:,::1] Ts_
48 |
49 | def __init__(self, float[:,:,::1] depthmaps, float[:,:,::1] Ks, float[:,:,::1] Rs, float[:,::1] Ts):
50 | cdef int n = depthmaps.shape[0]
51 | if n != Ks.shape[0]:
52 | raise Exception('number of depthmaps and Ks differ')
53 | if n != Rs.shape[0]:
54 | raise Exception('number of depthmaps and Rs differ')
55 | if n != Ts.shape[0]:
56 | raise Exception('number of depthmaps and Ts differ')
57 |
58 | if Ks.shape[1] != 3 or Ks.shape[2] != 3:
59 | raise Exception('Ks have to be nx3x3')
60 | if Rs.shape[1] != 3 or Rs.shape[2] != 3:
61 | raise Exception('Rs have to be nx3x3')
62 | if Ts.shape[1] != 3:
63 | raise Exception('Ts have to be nx3')
64 |
65 | self.depthmaps_ = depthmaps
66 | self.Ks_ = Ks
67 | self.Rs_ = Rs
68 | self.Ts_ = Ts
69 |
70 | self.views.depthmaps_ = &(depthmaps[0,0,0])
71 | self.views.n_views_ = depthmaps.shape[0]
72 | self.views.rows_ = depthmaps.shape[1]
73 | self.views.cols_ = depthmaps.shape[2]
74 | self.views.Ks_ = &(Ks[0,0,0])
75 | self.views.Rs_ = &(Rs[0,0,0])
76 | self.views.Ts_ = &(Ts[0,0])
77 |
78 |
79 | cdef class PyVolume:
80 | cdef Volume vol
81 |
82 | def __init__(self, float[:,:,:,::1] data):
83 | self.vol = Volume()
84 | self.vol.data_ = &(data[0,0,0,0])
85 | self.vol.channels_ = data.shape[0]
86 | self.vol.depth_ = data.shape[1]
87 | self.vol.height_ = data.shape[2]
88 | self.vol.width_ = data.shape[3]
89 |
90 |
91 | def projmask_cpu(PyViews views, int depth, int height, int width, float vx_size, bool unknown_is_free, int n_threads=8):
92 | vol = np.empty((1, depth, height, width), dtype=np.float32)
93 | cdef float[:,:,:,::1] vol_view = vol
94 | cdef PyVolume py_vol = PyVolume(vol_view)
95 | fusion_projectionmask_cpu(views.views, vx_size, unknown_is_free, n_threads, py_vol.vol)
96 | return vol
97 |
98 | def occupancy_cpu(PyViews views, int depth, int height, int width, float vx_size, float truncation, bool unknown_is_free, int n_threads=8):
99 | vol = np.empty((1, depth, height, width), dtype=np.float32)
100 | cdef float[:,:,:,::1] vol_view = vol
101 | cdef PyVolume py_vol = PyVolume(vol_view)
102 | fusion_occupancy_cpu(views.views, vx_size, truncation, unknown_is_free, n_threads, py_vol.vol)
103 | return vol
104 |
105 | def tsdfmask_cpu(PyViews views, int depth, int height, int width, float vx_size, float truncation, bool unknown_is_free, int n_threads=8):
106 | vol = np.empty((1, depth, height, width), dtype=np.float32)
107 | cdef float[:,:,:,::1] vol_view = vol
108 | cdef PyVolume py_vol = PyVolume(vol_view)
109 | fusion_tsdfmask_cpu(views.views, vx_size, truncation, unknown_is_free, n_threads, py_vol.vol)
110 | return vol
111 |
112 | def tsdf_cpu(PyViews views, int depth, int height, int width, float vx_size, float truncation, bool unknown_is_free, int n_threads=8):
113 | vol = np.empty((1, depth, height, width), dtype=np.float32)
114 | cdef float[:,:,:,::1] vol_view = vol
115 | cdef PyVolume py_vol = PyVolume(vol_view)
116 | fusion_tsdf_cpu(views.views, vx_size, truncation, unknown_is_free, n_threads, py_vol.vol)
117 | return vol
118 |
119 | def tsdf_hist_cpu(PyViews views, int depth, int height, int width, float vx_size, float truncation, bool unknown_is_free, float[::1] bins, bool unobserved_is_occupied=True, int n_threads=8):
120 | cdef int n_bins = bins.shape[0]
121 | vol = np.empty((n_bins, depth, height, width), dtype=np.float32)
122 | cdef float[:,:,:,::1] vol_view = vol
123 | cdef PyVolume py_vol = PyVolume(vol_view)
124 | fusion_tsdf_hist_cpu(views.views, vx_size, truncation, unknown_is_free, &(bins[0]), n_bins, unobserved_is_occupied, n_threads, py_vol.vol)
125 | return vol
--------------------------------------------------------------------------------
/watertight_transformer/external/libfusioncpu/fusion.cpp:
--------------------------------------------------------------------------------
1 | #include "fusion.h"
2 |
3 | #include
4 | #include
5 |
6 | #if defined(_OPENMP)
7 | #include
8 | #endif
9 |
10 |
11 | template
12 | void fusion_cpu(const Views& views, const FusionFunctorT functor, float vx_size, int n_threads, Volume& vol) {
13 | int vx_res3 = vol.depth_ * vol.height_ * vol.width_;
14 |
15 | #if defined(_OPENMP)
16 | omp_set_num_threads(n_threads);
17 | #endif
18 | #pragma omp parallel for
19 | for(int idx = 0; idx < vx_res3; ++idx) {
20 | int d,h,w;
21 | fusion_idx2dhw(idx, vol.width_,vol.height_, d,h,w);
22 | float x,y,z;
23 | fusion_dhw2xyz(d,h,w, vx_size, x,y,z);
24 |
25 | functor.before_sample(&vol, d,h,w);
26 | bool run = true;
27 | int n_valid_views = 0;
28 | for(int vidx = 0; vidx < views.n_views_ && run; ++vidx) {
29 | float ur, vr, vx_d;
30 | fusion_project(&views, vidx, x,y,z, ur,vr,vx_d);
31 |
32 | int u = int(ur + 0.5f);
33 | int v = int(vr + 0.5f);
34 | // printf(" vx %d,%d,%d has center %f,%f,%f and projects to uvd=%f,%f,%f\n", w,h,d, x,y,z, ur,vr,vx_d);
35 |
36 | if(u >= 0 && v >= 0 && u < views.cols_ && v < views.rows_) {
37 | int dm_idx = (vidx * views.rows_ + v) * views.cols_ + u;
38 | float dm_d = views.depthmaps_[dm_idx];
39 | // printf(" is on depthmap[%d,%d] with depth=%f, diff=%f\n", views.cols_,views.rows_, dm_d, dm_d - vx_d);
40 | run = functor.new_sample(&vol, vx_d, dm_d, d,h,w, &n_valid_views);
41 | }
42 | } // for vidx
43 | functor.after_sample(&vol, d,h,w, n_valid_views);
44 | }
45 | }
46 |
47 | void fusion_projectionmask_cpu(const Views& views, float vx_size, bool unknown_is_free, int n_threads, Volume& vol) {
48 | ProjectionMaskFusionFunctor functor(unknown_is_free);
49 | fusion_cpu(views, functor, vx_size, n_threads, vol);
50 | }
51 |
52 | void fusion_occupancy_cpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, int n_threads, Volume& vol) {
53 | OccupancyFusionFunctor functor(truncation, unknown_is_free);
54 | fusion_cpu(views, functor, vx_size, n_threads, vol);
55 | }
56 |
57 | void fusion_tsdfmask_cpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, int n_threads, Volume& vol) {
58 | TsdfMaskFusionFunctor functor(truncation, unknown_is_free);
59 | fusion_cpu(views, functor, vx_size, n_threads, vol);
60 | }
61 |
62 | void fusion_tsdf_cpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, int n_threads, Volume& vol) {
63 | TsdfFusionFunctor functor(truncation, unknown_is_free);
64 | fusion_cpu(views, functor, vx_size, n_threads, vol);
65 | }
66 |
67 | void fusion_tsdf_hist_cpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, float* bin_centers, int n_bins, bool unobserved_is_occupied, int n_threads, Volume& vol) {
68 | TsdfHistFusionFunctor functor(truncation, unknown_is_free, bin_centers, n_bins, unobserved_is_occupied);
69 | fusion_cpu(views, functor, vx_size, n_threads, vol);
70 | }
71 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libfusioncpu/fusion.h:
--------------------------------------------------------------------------------
1 | #ifndef FUSION_H
2 | #define FUSION_H
3 |
4 | #include
5 | #include
6 |
7 | #ifdef __CUDA_ARCH__
8 | #define FUSION_FUNCTION __host__ __device__
9 | #else
10 | #define FUSION_FUNCTION
11 | #endif
12 |
13 |
14 | class Views {
15 | public:
16 | int n_views_;
17 | float* depthmaps_;
18 | int rows_;
19 | int cols_;
20 | float* Ks_;
21 | float* Rs_;
22 | float* Ts_;
23 |
24 | Views() : n_views_(0), depthmaps_(0), rows_(0), cols_(0), Ks_(0), Rs_(0), Ts_(0) {}
25 | };
26 |
27 | class Volume {
28 | public:
29 | int channels_;
30 | int depth_;
31 | int height_;
32 | int width_;
33 | float* data_;
34 |
35 | Volume() : channels_(0), depth_(0), height_(0), width_(0), data_(0) {}
36 | };
37 |
38 | FUSION_FUNCTION
39 | inline int volume_idx(const Volume* vol, int c, int d, int h, int w) {
40 | return ((c * vol->depth_ + d) * vol->height_ + h) * vol->width_ + w;
41 | }
42 |
43 | FUSION_FUNCTION
44 | inline float volume_get(const Volume* vol, int c, int d, int h, int w) {
45 | return vol->data_[volume_idx(vol, c,d,h,w)];
46 | }
47 |
48 | FUSION_FUNCTION
49 | inline void volume_set(const Volume* vol, int c, int d, int h, int w, float val) {
50 | vol->data_[volume_idx(vol, c,d,h,w)] = val;
51 | }
52 |
53 | FUSION_FUNCTION
54 | inline void volume_add(const Volume* vol, int c, int d, int h, int w, float val) {
55 | vol->data_[volume_idx(vol, c,d,h,w)] += val;
56 | }
57 |
58 | FUSION_FUNCTION
59 | inline void volume_div(const Volume* vol, int c, int d, int h, int w, float val) {
60 | vol->data_[volume_idx(vol, c,d,h,w)] /= val;
61 | }
62 |
63 | struct FusionFunctor {
64 | FUSION_FUNCTION
65 | virtual bool new_sample(Volume* vol, float vx_depth, float dm_depth, int d, int h, int w, int* n_valid_views) const {
66 | return false;
67 | }
68 |
69 | FUSION_FUNCTION
70 | virtual void before_sample(Volume* vol, int d, int h, int w) const {
71 | for(int c = 0; c < vol->channels_; ++c) {
72 | volume_set(vol, c,d,h,w, 0);
73 | }
74 | }
75 |
76 | FUSION_FUNCTION
77 | virtual void after_sample(Volume* vol, int d, int h, int w, int n_valid_views) const {}
78 | };
79 |
80 |
81 |
82 |
83 | FUSION_FUNCTION
84 | inline void fusion_idx2dhw(int idx, int width, int height, int& d, int& h, int &w) {
85 | w = idx % (width);
86 | d = idx / (width * height);
87 | h = ((idx - w) / width) % height;
88 | }
89 |
90 | FUSION_FUNCTION
91 | inline void fusion_dhw2xyz(int d, int h, int w, float vx_size, float& x, float& y, float& z) {
92 | // +0.5: move vx_center from (0,0,0) to (0.5,0.5,0.5), therefore vx range in [0, 1)
93 | // *vx_size: scale from [0,vx_resolution) to [0,1)
94 | // -0.5: move box to center, resolution [-.5,0.5)
95 | x = ((w + 0.5) * vx_size) - 0.5;
96 | y = ((h + 0.5) * vx_size) - 0.5;
97 | z = ((d + 0.5) * vx_size) - 0.5;
98 | }
99 |
100 | FUSION_FUNCTION
101 | inline void fusion_project(const Views* views, int vidx, float x, float y, float z, float& u, float& v, float& d) {
102 | float* K = views->Ks_ + vidx * 9;
103 | float* R = views->Rs_ + vidx * 9;
104 | float* T = views->Ts_ + vidx * 3;
105 |
106 | float xt = R[0] * x + R[1] * y + R[2] * z + T[0];
107 | float yt = R[3] * x + R[4] * y + R[5] * z + T[1];
108 | float zt = R[6] * x + R[7] * y + R[8] * z + T[2];
109 | // printf(" vx has center %f,%f,%f and projects to %f,%f,%f\n", x,y,z, xt,yt,zt);
110 |
111 | u = K[0] * xt + K[1] * yt + K[2] * zt;
112 | v = K[3] * xt + K[4] * yt + K[5] * zt;
113 | d = K[6] * xt + K[7] * yt + K[8] * zt;
114 | u /= d;
115 | v /= d;
116 | }
117 |
118 |
119 | struct ProjectionMaskFusionFunctor : public FusionFunctor {
120 | bool unknown_is_free_;
121 | ProjectionMaskFusionFunctor(bool unknown_is_free) :
122 | unknown_is_free_(unknown_is_free) {}
123 |
124 | FUSION_FUNCTION
125 | virtual bool new_sample(Volume* vol, float vx_depth, float dm_depth, int d, int h, int w, int* n_valid_views) const {
126 | if(unknown_is_free_ && dm_depth < 0) {
127 | dm_depth = 1e9;
128 | }
129 | if(dm_depth > 0) {
130 | volume_set(vol, 0,d,h,w, 1);
131 | return false;
132 | }
133 | return true;
134 | }
135 | };
136 |
137 | void fusion_projectionmask_cpu(const Views& views, float vx_size, bool unknown_is_free, int n_threads, Volume& vol);
138 |
139 | struct OccupancyFusionFunctor : public FusionFunctor {
140 | float truncation_;
141 | bool unknown_is_free_;
142 | OccupancyFusionFunctor(float truncation, bool unknown_is_free) :
143 | truncation_(truncation), unknown_is_free_(unknown_is_free) {}
144 |
145 | FUSION_FUNCTION
146 | virtual void before_sample(Volume* vol, int d, int h, int w) const {
147 | for(int c = 0; c < vol->channels_; ++c) {
148 | volume_set(vol, c,d,h,w, 1);
149 | }
150 | }
151 |
152 | FUSION_FUNCTION
153 | virtual bool new_sample(Volume* vol, float vx_depth, float dm_depth, int d, int h, int w, int* n_valid_views) const {
154 | if(unknown_is_free_ && dm_depth < 0) {
155 | dm_depth = 1e9;
156 | }
157 | float diff = dm_depth - vx_depth;
158 | if(dm_depth > 0 && diff > truncation_) {
159 | volume_set(vol, 0,d,h,w, 0);
160 | return false;
161 | }
162 | return true;
163 | }
164 | };
165 |
166 | void fusion_occupancy_cpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, int n_threads, Volume& vol);
167 |
168 | struct TsdfMaskFusionFunctor : public FusionFunctor {
169 | float truncation_;
170 | bool unknown_is_free_;
171 | TsdfMaskFusionFunctor(float truncation, bool unknown_is_free) :
172 | truncation_(truncation), unknown_is_free_(unknown_is_free) {}
173 |
174 | FUSION_FUNCTION
175 | virtual bool new_sample(Volume* vol, float vx_depth, float dm_depth, int d, int h, int w, int* n_valid_views) const {
176 | if(unknown_is_free_ && dm_depth < 0) {
177 | dm_depth = 1e9;
178 | }
179 | float diff = dm_depth - vx_depth;
180 | if(dm_depth > 0 && diff >= -truncation_) {
181 | volume_set(vol, 0,d,h,w, 1);
182 | return false;
183 | }
184 | return true;
185 | }
186 | };
187 |
188 | void fusion_tsdfmask_cpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, int n_threads, Volume& vol);
189 |
190 | struct TsdfFusionFunctor : public FusionFunctor {
191 | float truncation_;
192 | bool unknown_is_free_;
193 | TsdfFusionFunctor(float truncation, bool unknown_is_free) :
194 | truncation_(truncation), unknown_is_free_(unknown_is_free) {}
195 |
196 | FUSION_FUNCTION
197 | virtual void before_sample(Volume* vol, int d, int h, int w) const {
198 | for(int c = 0; c < vol->channels_; ++c) {
199 | volume_set(vol, c,d,h,w, 0);
200 | }
201 | }
202 |
203 | FUSION_FUNCTION
204 | virtual bool new_sample(Volume* vol, float vx_depth, float dm_depth, int d, int h, int w, int* n_valid_views) const {
205 | if(unknown_is_free_ && dm_depth < 0) {
206 | dm_depth = 1e9;
207 | }
208 | float dist = dm_depth - vx_depth;
209 | float truncated_dist = fminf(truncation_, fmaxf(-truncation_, dist));
210 | if(dm_depth > 0 && dist >= -truncation_) {
211 | (*n_valid_views)++;
212 | volume_add(vol, 0,d,h,w, truncated_dist);
213 | }
214 | return true;
215 | }
216 |
217 | FUSION_FUNCTION
218 | virtual void after_sample(Volume* vol, int d, int h, int w, int n_valid_views) const {
219 | if(n_valid_views > 0) {
220 | volume_div(vol, 0,d,h,w, n_valid_views);
221 | }
222 | else {
223 | volume_set(vol, 0,d,h,w, -truncation_);
224 | }
225 | }
226 | };
227 |
228 | void fusion_tsdf_cpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, int n_threads, Volume& vol);
229 |
230 | struct TsdfHistFusionFunctor : public FusionFunctor {
231 | float truncation_;
232 | bool unknown_is_free_;
233 | float* bin_centers_;
234 | int n_bins_;
235 | bool unobserved_is_occupied_;
236 | TsdfHistFusionFunctor(float truncation, bool unknown_is_free, float* bin_centers, int n_bins, bool unobserved_is_occupied) :
237 | truncation_(truncation), unknown_is_free_(unknown_is_free), bin_centers_(bin_centers), n_bins_(n_bins), unobserved_is_occupied_(unobserved_is_occupied) {}
238 |
239 | FUSION_FUNCTION
240 | virtual bool new_sample(Volume* vol, float vx_depth, float dm_depth, int d, int h, int w, int* n_valid_views) const {
241 | if(unknown_is_free_ && dm_depth < 0) {
242 | dm_depth = 1e9;
243 | }
244 | float dist = dm_depth - vx_depth;
245 |
246 | if(dm_depth > 0 && dist >= -truncation_) {
247 | (*n_valid_views)++;
248 | if(dist <= bin_centers_[0]) {
249 | volume_add(vol, 0,d,h,w, 1);
250 | }
251 | else if(dist >= bin_centers_[n_bins_-1]) {
252 | volume_add(vol, n_bins_-1,d,h,w, 1);
253 | }
254 | else {
255 | int bin = 0;
256 | while(dist > bin_centers_[bin]) {
257 | bin++;
258 | }
259 | float a = fabs(bin_centers_[bin-1] - dist);
260 | float b = fabs(bin_centers_[bin] - dist);
261 | volume_add(vol, bin-1,d,h,w, a / (a+b));
262 | volume_add(vol, bin, d,h,w, b / (a+b));
263 | }
264 | }
265 | return true;
266 | }
267 |
268 | FUSION_FUNCTION
269 | virtual void after_sample(Volume* vol, int d, int h, int w, int n_valid_views) const {
270 | if(n_valid_views > 0) {
271 | for(int bin = 0; bin < n_bins_; ++bin) {
272 | volume_div(vol, bin,d,h,w, n_valid_views);
273 | }
274 | }
275 | else if(unobserved_is_occupied_) {
276 | volume_set(vol, 0,d,h,w, 1);
277 | }
278 | }
279 | };
280 |
281 | void fusion_tsdf_hist_cpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, float* bin_centers, int n_bins, bool unobserved_is_occupied, int n_threads, Volume& vol);
282 |
283 | #endif
284 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libfusiongpu/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2017, The OctNet authors
2 | # All rights reserved.
3 | #
4 | # Redistribution and use in source and binary forms, with or without
5 | # modification, are permitted provided that the following conditions are met:
6 | # * Redistributions of source code must retain the above copyright
7 | # notice, this list of conditions and the following disclaimer.
8 | # * Redistributions in binary form must reproduce the above copyright
9 | # notice, this list of conditions and the following disclaimer in the
10 | # documentation and/or other materials provided with the distribution.
11 | # * Neither the name of the nor the
12 | # names of its contributors may be used to endorse or promote products
13 | # derived from this software without specific prior written permission.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | # DISCLAIMED. IN NO EVENT SHALL OCTNET AUTHORS BE LIABLE FOR ANY
19 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
26 | cmake_minimum_required(VERSION 2.8)
27 | set(CMAKE_CXX_STANDARD 11)
28 |
29 | # set(CMAKE_BUILD_TYPE Debug)
30 | set(CMAKE_BUILD_TYPE Release)
31 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse -msse2 -msse3 -msse4.2 -fPIC")
32 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse -msse2 -msse3 -msse4.2 -fPIC")
33 |
34 | find_package(CUDA 6.5 REQUIRED)
35 | set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS};-std=c++11")
36 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_FORCE_INLINES -Wall")
37 |
38 | set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS}; -gencode=arch=compute_30,code=sm_30")
39 | set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS}; -gencode=arch=compute_30,code=compute_30")
40 |
41 | set(FUSION_GPU_SRC
42 | fusion.cu
43 | fusion_zach_tvl1.cu
44 | )
45 |
46 | cuda_add_library(fusion_gpu SHARED ${FUSION_GPU_SRC})
47 | target_link_libraries(fusion_gpu ${CUDA_LIBRARIES})
48 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libfusiongpu/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2017, Gernot
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libfusiongpu/README.md:
--------------------------------------------------------------------------------
1 | # PyFusion
2 |
3 | PyFusion is a Python framework for volumetric depth fusion.
4 | It contains simple occupancy and TSDF fusion methods that can be executed on a CPU as well as on a GPU.
5 |
6 | To use the code, first compile the native code via
7 |
8 | ```bash
9 | cd build
10 | cmake ..
11 | make
12 | ```
13 | Afterwards you can compile the Cython code via
14 |
15 | ```bash
16 | python setup.py build_ext --inplace
17 | ```
18 |
19 | You can then use the fusion functions
20 |
21 | ```python
22 | import pyfusion
23 |
24 | # create a views object
25 | # depthmaps: a NxHxW numpy float tensor of N depthmaps, invalid depth values are marked by negative numbers
26 | # Ks: the camera intric matrices, Nx3x3 float tensor
27 | # Rs: the camera rotation matrices, Nx3x3 float tensor
28 | # Ts: the camera translation vectors, Nx3 float tensor
29 | views = pyfusion.PyViews(depthmaps, Ks,Rs,Ts)
30 |
31 | # afterwards you can fuse the depth maps for example by
32 | # depth,height,width: number of voxels in each dimension
33 | # truncation: TSDF truncation value
34 | tsdf = pyfusion.tsdf_gpu(views, depth,height,width, vx_size, truncation, False)
35 |
36 | # the same code can also be run on the CPU
37 | tsdf = pyfusion.tsdf_cpu(views, depth,height,width, vx_size, truncation, False, n_threads=8)
38 | ```
39 |
40 | Make sure `pyfusion` is in your `$PYTHONPATH`.
41 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libfusiongpu/__init__.py:
--------------------------------------------------------------------------------
1 | import ctypes
2 | import os
3 |
4 | pyfusion_dir = os.path.dirname(os.path.realpath(__file__))
5 | ctypes.cdll.LoadLibrary(os.path.join(pyfusion_dir, 'build', 'libfusion_gpu.so'))
6 | from cyfusion import *
7 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libfusiongpu/cyfusion.pyx:
--------------------------------------------------------------------------------
1 | cimport cython
2 | import numpy as np
3 | cimport numpy as np
4 |
5 | from libc.stdlib cimport free, malloc
6 | from libcpp cimport bool
7 | from cpython cimport PyObject, Py_INCREF
8 |
9 | CREATE_INIT = True # workaround, so cython builds a init function
10 |
11 | np.import_array()
12 |
13 |
14 |
15 | cdef extern from "fusion.h":
16 | cdef cppclass Views:
17 | Views()
18 | int n_views_;
19 | float* depthmaps_;
20 | int rows_;
21 | int cols_;
22 | float* Ks_;
23 | float* Rs_;
24 | float* Ts_;
25 |
26 | cdef cppclass Volume:
27 | Volume()
28 | int channels_;
29 | int depth_;
30 | int height_;
31 | int width_;
32 | float* data_;
33 |
34 |
35 | void fusion_projectionmask_gpu(const Views& views, float vx_size, bool unknown_is_free, Volume& vol);
36 | void fusion_occupancy_gpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, Volume& vol);
37 | void fusion_tsdfmask_gpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, Volume& vol);
38 | void fusion_tsdf_gpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, Volume& vol);
39 | void fusion_tsdf_hist_gpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, float* bin_centers, int n_bins, bool unobserved_is_occupied, Volume& vol);
40 |
41 | void fusion_hist_zach_tvl1_gpu(const Volume& hist, bool hist_on_gpu, float truncation, float lambda_param, int iterations, Volume& vol);
42 | void fusion_zach_tvl1_gpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, float* bin_centers, int n_bins, float lambda_param, int iterations, Volume& vol);
43 |
44 |
45 | cdef class PyViews:
46 | cdef Views views
47 | # need to keep reference, otherwise it could get garbage collected
48 | cdef float[:,:,::1] depthmaps_
49 | cdef float[:,:,::1] Ks_
50 | cdef float[:,:,::1] Rs_
51 | cdef float[:,::1] Ts_
52 |
53 | def __init__(self, float[:,:,::1] depthmaps, float[:,:,::1] Ks, float[:,:,::1] Rs, float[:,::1] Ts):
54 | cdef int n = depthmaps.shape[0]
55 | if n != Ks.shape[0]:
56 | raise Exception('number of depthmaps and Ks differ')
57 | if n != Rs.shape[0]:
58 | raise Exception('number of depthmaps and Rs differ')
59 | if n != Ts.shape[0]:
60 | raise Exception('number of depthmaps and Ts differ')
61 |
62 | if Ks.shape[1] != 3 or Ks.shape[2] != 3:
63 | raise Exception('Ks have to be nx3x3')
64 | if Rs.shape[1] != 3 or Rs.shape[2] != 3:
65 | raise Exception('Rs have to be nx3x3')
66 | if Ts.shape[1] != 3:
67 | raise Exception('Ts have to be nx3')
68 |
69 | self.depthmaps_ = depthmaps
70 | self.Ks_ = Ks
71 | self.Rs_ = Rs
72 | self.Ts_ = Ts
73 |
74 | self.views.depthmaps_ = &(depthmaps[0,0,0])
75 | self.views.n_views_ = depthmaps.shape[0]
76 | self.views.rows_ = depthmaps.shape[1]
77 | self.views.cols_ = depthmaps.shape[2]
78 | self.views.Ks_ = &(Ks[0,0,0])
79 | self.views.Rs_ = &(Rs[0,0,0])
80 | self.views.Ts_ = &(Ts[0,0])
81 |
82 |
83 | cdef class PyVolume:
84 | cdef Volume vol
85 |
86 | def __init__(self, float[:,:,:,::1] data):
87 | self.vol = Volume()
88 | self.vol.data_ = &(data[0,0,0,0])
89 | self.vol.channels_ = data.shape[0]
90 | self.vol.depth_ = data.shape[1]
91 | self.vol.height_ = data.shape[2]
92 | self.vol.width_ = data.shape[3]
93 |
94 |
95 | def projmask_gpu(PyViews views, int depth, int height, int width, float vx_size, bool unknown_is_free):
96 | vol = np.empty((1, depth, height, width), dtype=np.float32)
97 | cdef float[:,:,:,::1] vol_view = vol
98 | cdef PyVolume py_vol = PyVolume(vol_view)
99 | fusion_projectionmask_gpu(views.views, vx_size, unknown_is_free, py_vol.vol)
100 | return vol
101 | def occupancy_gpu(PyViews views, int depth, int height, int width, float vx_size, float truncation, bool unknown_is_free):
102 | vol = np.empty((1, depth, height, width), dtype=np.float32)
103 | cdef float[:,:,:,::1] vol_view = vol
104 | cdef PyVolume py_vol = PyVolume(vol_view)
105 | fusion_occupancy_gpu(views.views, vx_size, truncation, unknown_is_free, py_vol.vol)
106 | return vol
107 |
108 | def tsdfmask_gpu(PyViews views, int depth, int height, int width, float vx_size, float truncation, bool unknown_is_free):
109 | vol = np.empty((1, depth, height, width), dtype=np.float32)
110 | cdef float[:,:,:,::1] vol_view = vol
111 | cdef PyVolume py_vol = PyVolume(vol_view)
112 | fusion_tsdfmask_gpu(views.views, vx_size, truncation, unknown_is_free, py_vol.vol)
113 | return vol
114 |
115 | def tsdf_gpu(PyViews views, int depth, int height, int width, float vx_size, float truncation, bool unknown_is_free):
116 | vol = np.empty((1, depth, height, width), dtype=np.float32)
117 | cdef float[:,:,:,::1] vol_view = vol
118 | cdef PyVolume py_vol = PyVolume(vol_view)
119 | fusion_tsdf_gpu(views.views, vx_size, truncation, unknown_is_free, py_vol.vol)
120 | return vol
121 |
122 | def tsdf_hist_gpu(PyViews views, int depth, int height, int width, float vx_size, float truncation, bool unknown_is_free, float[::1] bins, bool unobserved_is_occupied=True):
123 | cdef int n_bins = bins.shape[0]
124 | vol = np.empty((n_bins, depth, height, width), dtype=np.float32)
125 | cdef float[:,:,:,::1] vol_view = vol
126 | cdef PyVolume py_vol = PyVolume(vol_view)
127 | fusion_tsdf_hist_gpu(views.views, vx_size, truncation, unknown_is_free, &(bins[0]), n_bins, unobserved_is_occupied, py_vol.vol)
128 | return vol
129 |
130 |
131 |
132 | def zach_tvl1_hist(float[:,:,:,::1] hist, float truncation, float lambda_param, int iterations, init=None):
133 | vol = np.zeros((1, hist.shape[1], hist.shape[2], hist.shape[3]), dtype=np.float32)
134 | if init is not None:
135 | vol[...] = init.reshape(vol.shape)
136 | cdef float[:,:,:,::1] vol_view = vol
137 | cdef PyVolume py_vol = PyVolume(vol_view)
138 | cdef PyVolume py_hist = PyVolume(hist)
139 | fusion_hist_zach_tvl1_gpu(py_hist.vol, False, truncation, lambda_param, iterations, py_vol.vol)
140 | return vol
141 |
142 | def zach_tvl1(PyViews views, int depth, int height, int width, float vx_size, float truncation, bool unknown_is_free, float[::1] bins, float lambda_param, int iterations, init=None):
143 | cdef int n_bins = bins.shape[0]
144 | vol = np.zeros((1, depth, height, width), dtype=np.float32)
145 | if init is not None:
146 | vol[...] = init.reshape(vol.shape)
147 | cdef float[:,:,:,::1] vol_view = vol
148 | cdef PyVolume py_vol = PyVolume(vol_view)
149 | fusion_zach_tvl1_gpu(views.views, vx_size, truncation, unknown_is_free, &(bins[0]), n_bins, lambda_param, iterations, py_vol.vol)
150 | return vol
151 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libfusiongpu/fusion.cu:
--------------------------------------------------------------------------------
1 | #include "gpu_common.h"
2 |
3 | #include
4 | #include
5 |
6 |
7 |
8 | template
9 | __global__ void kernel_fusion(int vx_res3, const Views views, const FusionFunctorT functor, float vx_size, Volume vol) {
10 | CUDA_KERNEL_LOOP(idx, vx_res3) {
11 | int d,h,w;
12 | fusion_idx2dhw(idx, vol.width_,vol.height_, d,h,w);
13 | float x,y,z;
14 | fusion_dhw2xyz(d,h,w, vx_size, x,y,z);
15 |
16 | functor.before_sample(&vol, d,h,w);
17 | bool run = true;
18 | int n_valid_views = 0;
19 | for(int vidx = 0; vidx < views.n_views_ && run; ++vidx) {
20 | float ur, vr, vx_d;
21 | fusion_project(&views, vidx, x,y,z, ur,vr,vx_d);
22 | //NOTE: ur,vr,vx_d might differ to CPP (subtle differences in precision)
23 |
24 | int u = int(ur + 0.5f);
25 | int v = int(vr + 0.5f);
26 |
27 | if(u >= 0 && v >= 0 && u < views.cols_ && v < views.rows_) {
28 | int dm_idx = (vidx * views.rows_ + v) * views.cols_ + u;
29 | float dm_d = views.depthmaps_[dm_idx];
30 | // if(d==103 && h==130 && w==153) printf(" dm_d=%f, dm_idx=%d, u=%d, v=%d, ur=%f, vr=%f\n", dm_d, dm_idx, u,v, ur,vr);
31 | run = functor.new_sample(&vol, vx_d, dm_d, d,h,w, &n_valid_views);
32 | }
33 | } // for vidx
34 | functor.after_sample(&vol, d,h,w, n_valid_views);
35 | }
36 | }
37 |
38 |
39 |
40 | template
41 | void fusion_gpu(const Views& views, const FusionFunctorT functor, float vx_size, Volume& vol) {
42 | Views views_gpu;
43 | views_to_gpu(views, views_gpu, true);
44 | Volume vol_gpu;
45 | volume_alloc_like_gpu(vol, vol_gpu);
46 |
47 | int vx_res3 = vol.depth_ * vol.height_ * vol.width_;
48 | kernel_fusion<<>>(
49 | vx_res3, views_gpu, functor, vx_size, vol_gpu
50 | );
51 | CUDA_POST_KERNEL_CHECK;
52 |
53 | volume_to_cpu(vol_gpu, vol, false);
54 |
55 | views_free_gpu(views_gpu);
56 | volume_free_gpu(vol_gpu);
57 | }
58 |
59 | void fusion_projectionmask_gpu(const Views& views, float vx_size, bool unknown_is_free, Volume& vol) {
60 | ProjectionMaskFusionFunctor functor(unknown_is_free);
61 | fusion_gpu(views, functor, vx_size, vol);
62 | }
63 |
64 | void fusion_occupancy_gpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, Volume& vol) {
65 | OccupancyFusionFunctor functor(truncation, unknown_is_free);
66 | fusion_gpu(views, functor, vx_size, vol);
67 | }
68 |
69 | void fusion_tsdfmask_gpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, Volume& vol) {
70 | TsdfMaskFusionFunctor functor(truncation, unknown_is_free);
71 | fusion_gpu(views, functor, vx_size, vol);
72 | }
73 |
74 | void fusion_tsdf_gpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, Volume& vol) {
75 | TsdfFusionFunctor functor(truncation, unknown_is_free);
76 | fusion_gpu(views, functor, vx_size, vol);
77 | }
78 |
79 | void fusion_tsdf_hist_gpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, float* bin_centers, int n_bins, bool unobserved_is_occupied, Volume& vol) {
80 | float* bin_centers_gpu = host_to_device_malloc(bin_centers, n_bins);
81 | TsdfHistFusionFunctor functor(truncation, unknown_is_free, bin_centers_gpu, n_bins, unobserved_is_occupied);
82 | fusion_gpu(views, functor, vx_size, vol);
83 | device_free(bin_centers_gpu);
84 | }
85 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libfusiongpu/fusion.h:
--------------------------------------------------------------------------------
1 | #ifndef FUSION_H
2 | #define FUSION_H
3 |
4 | #include
5 | #include
6 |
7 | #ifdef __CUDA_ARCH__
8 | #define FUSION_FUNCTION __host__ __device__
9 | #else
10 | #define FUSION_FUNCTION
11 | #endif
12 |
13 |
14 | class Views {
15 | public:
16 | int n_views_;
17 | float* depthmaps_;
18 | int rows_;
19 | int cols_;
20 | float* Ks_;
21 | float* Rs_;
22 | float* Ts_;
23 |
24 | Views() : n_views_(0), depthmaps_(0), rows_(0), cols_(0), Ks_(0), Rs_(0), Ts_(0) {}
25 | };
26 |
27 | class Volume {
28 | public:
29 | int channels_;
30 | int depth_;
31 | int height_;
32 | int width_;
33 | float* data_;
34 |
35 | Volume() : channels_(0), depth_(0), height_(0), width_(0), data_(0) {}
36 | };
37 |
38 | FUSION_FUNCTION
39 | inline int volume_idx(const Volume* vol, int c, int d, int h, int w) {
40 | return ((c * vol->depth_ + d) * vol->height_ + h) * vol->width_ + w;
41 | }
42 |
43 | FUSION_FUNCTION
44 | inline float volume_get(const Volume* vol, int c, int d, int h, int w) {
45 | return vol->data_[volume_idx(vol, c,d,h,w)];
46 | }
47 |
48 | FUSION_FUNCTION
49 | inline void volume_set(const Volume* vol, int c, int d, int h, int w, float val) {
50 | vol->data_[volume_idx(vol, c,d,h,w)] = val;
51 | }
52 |
53 | FUSION_FUNCTION
54 | inline void volume_add(const Volume* vol, int c, int d, int h, int w, float val) {
55 | vol->data_[volume_idx(vol, c,d,h,w)] += val;
56 | }
57 |
58 | FUSION_FUNCTION
59 | inline void volume_div(const Volume* vol, int c, int d, int h, int w, float val) {
60 | vol->data_[volume_idx(vol, c,d,h,w)] /= val;
61 | }
62 |
63 | struct FusionFunctor {
64 | FUSION_FUNCTION
65 | virtual bool new_sample(Volume* vol, float vx_depth, float dm_depth, int d, int h, int w, int* n_valid_views) const {
66 | return false;
67 | }
68 |
69 | FUSION_FUNCTION
70 | virtual void before_sample(Volume* vol, int d, int h, int w) const {
71 | for(int c = 0; c < vol->channels_; ++c) {
72 | volume_set(vol, c,d,h,w, 0);
73 | }
74 | }
75 |
76 | FUSION_FUNCTION
77 | virtual void after_sample(Volume* vol, int d, int h, int w, int n_valid_views) const {}
78 | };
79 |
80 |
81 |
82 |
83 | FUSION_FUNCTION
84 | inline void fusion_idx2dhw(int idx, int width, int height, int& d, int& h, int &w) {
85 | w = idx % (width);
86 | d = idx / (width * height);
87 | h = ((idx - w) / width) % height;
88 | }
89 |
90 | FUSION_FUNCTION
91 | inline void fusion_dhw2xyz(int d, int h, int w, float vx_size, float& x, float& y, float& z) {
92 | // +0.5: move vx_center from (0,0,0) to (0.5,0.5,0.5), therefore vx range in [0, 1)
93 | // *vx_size: scale from [0,vx_resolution) to [0,1)
94 | // -0.5: move box to center, resolution [-.5,0.5)
95 | x = ((w + 0.5) * vx_size) - 0.5;
96 | y = ((h + 0.5) * vx_size) - 0.5;
97 | z = ((d + 0.5) * vx_size) - 0.5;
98 | }
99 |
100 | FUSION_FUNCTION
101 | inline void fusion_project(const Views* views, int vidx, float x, float y, float z, float& u, float& v, float& d) {
102 | float* K = views->Ks_ + vidx * 9;
103 | float* R = views->Rs_ + vidx * 9;
104 | float* T = views->Ts_ + vidx * 3;
105 |
106 | float xt = R[0] * x + R[1] * y + R[2] * z + T[0];
107 | float yt = R[3] * x + R[4] * y + R[5] * z + T[1];
108 | float zt = R[6] * x + R[7] * y + R[8] * z + T[2];
109 | // printf(" vx has center %f,%f,%f and projects to %f,%f,%f\n", x,y,z, xt,yt,zt);
110 |
111 | u = K[0] * xt + K[1] * yt + K[2] * zt;
112 | v = K[3] * xt + K[4] * yt + K[5] * zt;
113 | d = K[6] * xt + K[7] * yt + K[8] * zt;
114 | u /= d;
115 | v /= d;
116 | }
117 |
118 |
119 | struct ProjectionMaskFusionFunctor : public FusionFunctor {
120 | bool unknown_is_free_;
121 | ProjectionMaskFusionFunctor(bool unknown_is_free) :
122 | unknown_is_free_(unknown_is_free) {}
123 |
124 | FUSION_FUNCTION
125 | virtual bool new_sample(Volume* vol, float vx_depth, float dm_depth, int d, int h, int w, int* n_valid_views) const {
126 | if(unknown_is_free_ && dm_depth < 0) {
127 | dm_depth = 1e9;
128 | }
129 | if(dm_depth > 0) {
130 | volume_set(vol, 0,d,h,w, 1);
131 | return false;
132 | }
133 | return true;
134 | }
135 | };
136 |
137 | void fusion_projectionmask_gpu(const Views& views, float vx_size, bool unknown_is_free, Volume& vol);
138 |
139 |
140 | struct OccupancyFusionFunctor : public FusionFunctor {
141 | float truncation_;
142 | bool unknown_is_free_;
143 | OccupancyFusionFunctor(float truncation, bool unknown_is_free) :
144 | truncation_(truncation), unknown_is_free_(unknown_is_free) {}
145 |
146 | FUSION_FUNCTION
147 | virtual void before_sample(Volume* vol, int d, int h, int w) const {
148 | for(int c = 0; c < vol->channels_; ++c) {
149 | volume_set(vol, c,d,h,w, 1);
150 | }
151 | }
152 |
153 | FUSION_FUNCTION
154 | virtual bool new_sample(Volume* vol, float vx_depth, float dm_depth, int d, int h, int w, int* n_valid_views) const {
155 | if(unknown_is_free_ && dm_depth < 0) {
156 | dm_depth = 1e9;
157 | }
158 | float diff = dm_depth - vx_depth;
159 | if(dm_depth > 0 && diff > truncation_) {
160 | volume_set(vol, 0,d,h,w, 0);
161 | return false;
162 | }
163 | return true;
164 | }
165 | };
166 |
167 | void fusion_occupancy_gpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, Volume& vol);
168 |
169 |
170 |
171 | struct TsdfMaskFusionFunctor : public FusionFunctor {
172 | float truncation_;
173 | bool unknown_is_free_;
174 | TsdfMaskFusionFunctor(float truncation, bool unknown_is_free) :
175 | truncation_(truncation), unknown_is_free_(unknown_is_free) {}
176 |
177 | FUSION_FUNCTION
178 | virtual bool new_sample(Volume* vol, float vx_depth, float dm_depth, int d, int h, int w, int* n_valid_views) const {
179 | if(unknown_is_free_ && dm_depth < 0) {
180 | dm_depth = 1e9;
181 | }
182 | float diff = dm_depth - vx_depth;
183 | if(dm_depth > 0 && diff >= -truncation_) {
184 | volume_set(vol, 0,d,h,w, 1);
185 | return false;
186 | }
187 | return true;
188 | }
189 | };
190 |
191 | void fusion_tsdfmask_gpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, Volume& vol);
192 |
193 |
194 | struct TsdfFusionFunctor : public FusionFunctor {
195 | float truncation_;
196 | bool unknown_is_free_;
197 | TsdfFusionFunctor(float truncation, bool unknown_is_free) :
198 | truncation_(truncation), unknown_is_free_(unknown_is_free) {}
199 |
200 | FUSION_FUNCTION
201 | virtual void before_sample(Volume* vol, int d, int h, int w) const {
202 | for(int c = 0; c < vol->channels_; ++c) {
203 | volume_set(vol, c,d,h,w, 0);
204 | }
205 | }
206 |
207 | FUSION_FUNCTION
208 | virtual bool new_sample(Volume* vol, float vx_depth, float dm_depth, int d, int h, int w, int* n_valid_views) const {
209 | if(unknown_is_free_ && dm_depth < 0) {
210 | dm_depth = 1e9;
211 | }
212 | float dist = dm_depth - vx_depth;
213 | float truncated_dist = fminf(truncation_, fmaxf(-truncation_, dist));
214 | if(dm_depth > 0 && dist >= -truncation_) {
215 | (*n_valid_views)++;
216 | volume_add(vol, 0,d,h,w, truncated_dist);
217 | }
218 | return true;
219 | }
220 |
221 | FUSION_FUNCTION
222 | virtual void after_sample(Volume* vol, int d, int h, int w, int n_valid_views) const {
223 | if(n_valid_views > 0) {
224 | volume_div(vol, 0,d,h,w, n_valid_views);
225 | }
226 | else {
227 | volume_set(vol, 0,d,h,w, -truncation_);
228 | }
229 | }
230 | };
231 |
232 | void fusion_tsdf_gpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, Volume& vol);
233 |
234 |
235 | struct TsdfHistFusionFunctor : public FusionFunctor {
236 | float truncation_;
237 | bool unknown_is_free_;
238 | float* bin_centers_;
239 | int n_bins_;
240 | bool unobserved_is_occupied_;
241 | TsdfHistFusionFunctor(float truncation, bool unknown_is_free, float* bin_centers, int n_bins, bool unobserved_is_occupied) :
242 | truncation_(truncation), unknown_is_free_(unknown_is_free), bin_centers_(bin_centers), n_bins_(n_bins), unobserved_is_occupied_(unobserved_is_occupied) {}
243 |
244 | FUSION_FUNCTION
245 | virtual bool new_sample(Volume* vol, float vx_depth, float dm_depth, int d, int h, int w, int* n_valid_views) const {
246 | if(unknown_is_free_ && dm_depth < 0) {
247 | dm_depth = 1e9;
248 | }
249 | float dist = dm_depth - vx_depth;
250 |
251 | if(dm_depth > 0 && dist >= -truncation_) {
252 | (*n_valid_views)++;
253 | if(dist <= bin_centers_[0]) {
254 | volume_add(vol, 0,d,h,w, 1);
255 | }
256 | else if(dist >= bin_centers_[n_bins_-1]) {
257 | volume_add(vol, n_bins_-1,d,h,w, 1);
258 | }
259 | else {
260 | int bin = 0;
261 | while(dist > bin_centers_[bin]) {
262 | bin++;
263 | }
264 | float a = fabs(bin_centers_[bin-1] - dist);
265 | float b = fabs(bin_centers_[bin] - dist);
266 | volume_add(vol, bin-1,d,h,w, a / (a+b));
267 | volume_add(vol, bin, d,h,w, b / (a+b));
268 | }
269 | }
270 | return true;
271 | }
272 |
273 | FUSION_FUNCTION
274 | virtual void after_sample(Volume* vol, int d, int h, int w, int n_valid_views) const {
275 | if(n_valid_views > 0) {
276 | for(int bin = 0; bin < n_bins_; ++bin) {
277 | volume_div(vol, bin,d,h,w, n_valid_views);
278 | }
279 | }
280 | else if(unobserved_is_occupied_) {
281 | volume_set(vol, 0,d,h,w, 1);
282 | }
283 | }
284 | };
285 |
286 | void fusion_tsdf_hist_gpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, float* bin_centers, int n_bins, bool unobserved_is_occupied, Volume& vol);
287 |
288 |
289 |
290 |
291 | void fusion_hist_zach_tvl1_gpu(const Volume& hist, bool hist_on_gpu, float truncation, float lambda, int iterations, Volume& vol);
292 | void fusion_zach_tvl1_gpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, float* bin_centers, int n_bins, float lambda, int iterations, Volume& vol);
293 |
294 | #endif
295 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libfusiongpu/fusion_zach_tvl1.cu:
--------------------------------------------------------------------------------
1 | #include "gpu_common.h"
2 |
3 | __global__ void kernel_zach_tvl1_dual(Volume u, Volume p, Volume hist, int vx_res3, float sigma) {
4 | CUDA_KERNEL_LOOP(idx, vx_res3) {
5 | int d,h,w;
6 | fusion_idx2dhw(idx, u.width_,u.height_, d,h,w);
7 |
8 | float u_curr = u.data_[idx];
9 |
10 | float u_x = u.data_[idx + (w < u.width_-1)] - u_curr;
11 | float u_y = u.data_[idx + (h < u.height_-1) * u.width_] - u_curr;
12 | float u_z = u.data_[idx + (d < u.depth_-1) * u.width_ * u.height_] - u_curr;
13 |
14 | float p0_new = p.data_[idx + 0 * vx_res3] + sigma * u_x;
15 | float p1_new = p.data_[idx + 1 * vx_res3] + sigma * u_y;
16 | float p2_new = p.data_[idx + 2 * vx_res3] + sigma * u_z;
17 |
18 | float denom = fmax(1.0f, sqrtf(p0_new*p0_new + p1_new*p1_new + p2_new*p2_new));
19 |
20 | p.data_[idx + 0 * vx_res3] = p0_new / denom;
21 | p.data_[idx + 1 * vx_res3] = p1_new / denom;
22 | p.data_[idx + 2 * vx_res3] = p2_new / denom;
23 | }
24 | }
25 |
26 | __global__ void kernel_zach_tvl1_primal(Volume u, Volume p, Volume hist, int vx_res3, float tau, float lambda) {
27 | CUDA_KERNEL_LOOP(idx, vx_res3) {
28 | int d,h,w;
29 | fusion_idx2dhw(idx, u.width_,u.height_, d,h,w);
30 |
31 | float px = (w > 0) * p.data_[idx + 0 * vx_res3 - (w>0)] - p.data_[idx + 0 * vx_res3];
32 | float py = (h > 0) * p.data_[idx + 1 * vx_res3 - (h>0) * u.width_] - p.data_[idx + 1 * vx_res3];
33 | float pz = (d > 0) * p.data_[idx + 2 * vx_res3 - (d>0) * u.width_ * u.height_] - p.data_[idx + 2 * vx_res3];
34 |
35 | float u_old = u.data_[idx];
36 |
37 | float divergence = px + py + pz;
38 | float u_new = u_old - tau * divergence;
39 |
40 |
41 | int n_bins = hist.channels_;
42 | extern __shared__ float shared[];
43 | float* arr_w = shared + threadIdx.x * 2 * (n_bins + 1);
44 | float* arr_W = arr_w + (n_bins + 1);
45 | float* arr_l = arr_w;
46 | for(int i = 0; i < n_bins; ++i) {
47 | arr_w[i] = hist.data_[i * vx_res3 + idx];
48 | }
49 |
50 | for(int i = 0; i <= n_bins; ++i) {
51 | arr_W[i] = 0;
52 | for(int j = 1; j <= i; ++j) {
53 | arr_W[i] -= arr_w[j-1];
54 | }
55 | for(int j = i+1; j <= n_bins; ++j) {
56 | arr_W[i] += arr_w[j-1];
57 | }
58 | }
59 |
60 | for(int i = 0; i < n_bins; ++i) {
61 | arr_l[i] = ((2.0f * i) / (n_bins - 1.0f)) - 1.0f;
62 | }
63 | arr_l[n_bins] = 1e9;
64 |
65 |
66 | for(int i = 0; i <= n_bins; ++i) {
67 | float p = u_new + tau * lambda * arr_W[i];
68 | for (int j = n_bins; j >= 0; j--) {
69 | if (p < arr_l[j]) {
70 | float tmp = arr_l[j];
71 | arr_l[j] = p;
72 | if (j < n_bins) {
73 | arr_l[j+1] = tmp;
74 | }
75 | } else {
76 | break;
77 | }
78 | }
79 | }
80 |
81 | u.data_[idx] = fminf(1.0f, fmaxf(-1.0f, arr_l[n_bins]));
82 | }
83 | }
84 |
85 |
86 |
87 | void fusion_hist_zach_tvl1_gpu(const Volume& hist, bool hist_on_gpu, float truncation, float lambda, int iterations, Volume& vol) {
88 | Volume hist_gpu;
89 | if(hist_on_gpu) {
90 | hist_gpu = hist;
91 | }
92 | else {
93 | volume_to_gpu(hist, hist_gpu, true);
94 | }
95 | int n_bins = hist.channels_;
96 | int vx_res3 = hist.depth_ * hist.height_ * hist.width_;
97 |
98 |
99 | // primal-dual algorithm
100 | Volume u, p;
101 | volume_to_gpu(vol, u, true);
102 | volume_alloc_gpu(3, vol.depth_,vol.height_,vol.width_, p);
103 | volume_fill_data_gpu(p, 0);
104 |
105 | float tau = 1.0/sqrt(6.0f)/3;
106 | float sigma = 1.0/sqrt(6.0f)*3;
107 |
108 | for(int iter = 0; iter < iterations; ++iter) {
109 | if((iter+1) % 100 == 0) {
110 | printf(" zach_tvl1 iter=% 4d\n", iter+1);
111 | }
112 | kernel_zach_tvl1_dual<<>>(
113 | u, p, hist_gpu, vx_res3, sigma
114 | );
115 | kernel_zach_tvl1_primal<<>>(
116 | u, p, hist_gpu, vx_res3, tau, lambda
117 | );
118 | cudaDeviceSynchronize();
119 | }
120 | CUDA_POST_KERNEL_CHECK;
121 |
122 | volume_mul_data_gpu(u, truncation);
123 |
124 | volume_to_cpu(u, vol, false);
125 |
126 | volume_free_gpu(u);
127 | volume_free_gpu(p);
128 |
129 |
130 | if(!hist_on_gpu) {
131 | volume_free_gpu(hist_gpu);
132 | }
133 | }
134 |
135 | void fusion_zach_tvl1_gpu(const Views& views, float vx_size, float truncation, bool unknown_is_free, float* bin_centers, int n_bins, float lambda, int iterations, Volume& vol) {
136 | //compute hist
137 | Views views_gpu;
138 | views_to_gpu(views, views_gpu, true);
139 |
140 | Volume hist;
141 | float* bin_centers_gpu = host_to_device_malloc(bin_centers, n_bins);
142 | volume_alloc_gpu(n_bins, vol.depth_, vol.height_, vol.width_, hist);
143 | bool unobserved_is_occupied = true;
144 | TsdfHistFusionFunctor functor(truncation, unknown_is_free, bin_centers_gpu, n_bins, unobserved_is_occupied);
145 | device_free(bin_centers_gpu);
146 |
147 | int vx_res3 = vol.depth_ * vol.height_ * vol.width_;
148 | kernel_fusion<<>>(
149 | vx_res3, views_gpu, functor, vx_size, hist
150 | );
151 | CUDA_POST_KERNEL_CHECK;
152 |
153 | views_free_gpu(views_gpu);
154 |
155 | fusion_hist_zach_tvl1_gpu(hist, true, truncation, lambda, iterations, vol);
156 | }
157 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libfusiongpu/gpu_common.h:
--------------------------------------------------------------------------------
1 | #include "fusion.h"
2 |
3 | #include
4 |
5 | #include
6 | // #include
7 | // #include
8 | // #include
9 | // #include
10 | // #include
11 |
12 |
13 | #define DEBUG 0
14 | #define CUDA_DEBUG_DEVICE_SYNC 0
15 |
16 | inline void getCudaMemUsage(size_t* free_byte, size_t* total_byte) {
17 | cudaError_t stat = cudaMemGetInfo(free_byte, total_byte);
18 | if(stat != cudaSuccess) {
19 | printf("[ERROR] failed to query cuda memory (%f,%f) information, %s\n", *free_byte/1024.0/1024.0, *total_byte/1024.0/1024.0, cudaGetErrorString(stat));
20 | exit(-1);
21 | }
22 | }
23 |
24 | inline void printCudaMemUsage() {
25 | size_t free_byte, total_byte;
26 | getCudaMemUsage(&free_byte, &total_byte);
27 | printf("[INFO] CUDA MEM Free=%f[MB], Used=%f[MB]\n", free_byte/1024.0/1024.0, total_byte/1024.0/1024.0);
28 | }
29 |
30 | // cuda check for cudaMalloc and so on
31 | #define CUDA_CHECK(condition) \
32 | /* Code block avoids redefinition of cudaError_t error */ \
33 | do { \
34 | if(CUDA_DEBUG_DEVICE_SYNC) { cudaDeviceSynchronize(); } \
35 | cudaError_t error = condition; \
36 | if(error != cudaSuccess) { \
37 | printf("%s in %s at %d\n", cudaGetErrorString(error), __FILE__, __LINE__); \
38 | exit(-1); \
39 | } \
40 | } while (0)
41 |
42 |
43 | inline const char* cublasGetErrorString(cublasStatus_t error) {
44 | switch (error) {
45 | case CUBLAS_STATUS_SUCCESS:
46 | return "CUBLAS_STATUS_SUCCESS";
47 | case CUBLAS_STATUS_NOT_INITIALIZED:
48 | return "CUBLAS_STATUS_NOT_INITIALIZED";
49 | case CUBLAS_STATUS_ALLOC_FAILED:
50 | return "CUBLAS_STATUS_ALLOC_FAILED";
51 | case CUBLAS_STATUS_INVALID_VALUE:
52 | return "CUBLAS_STATUS_INVALID_VALUE";
53 | case CUBLAS_STATUS_ARCH_MISMATCH:
54 | return "CUBLAS_STATUS_ARCH_MISMATCH";
55 | case CUBLAS_STATUS_MAPPING_ERROR:
56 | return "CUBLAS_STATUS_MAPPING_ERROR";
57 | case CUBLAS_STATUS_EXECUTION_FAILED:
58 | return "CUBLAS_STATUS_EXECUTION_FAILED";
59 | case CUBLAS_STATUS_INTERNAL_ERROR:
60 | return "CUBLAS_STATUS_INTERNAL_ERROR";
61 | case CUBLAS_STATUS_NOT_SUPPORTED:
62 | return "CUBLAS_STATUS_NOT_SUPPORTED";
63 | case CUBLAS_STATUS_LICENSE_ERROR:
64 | return "CUBLAS_STATUS_LICENSE_ERROR";
65 | }
66 | return "Unknown cublas status";
67 | }
68 |
69 | #define CUBLAS_CHECK(condition) \
70 | do { \
71 | if(CUDA_DEBUG_DEVICE_SYNC) { cudaDeviceSynchronize(); } \
72 | cublasStatus_t status = condition; \
73 | if(status != CUBLAS_STATUS_SUCCESS) { \
74 | printf("%s in %s at %d\n", cublasGetErrorString(status), __FILE__, __LINE__); \
75 | exit(-1); \
76 | } \
77 | } while (0)
78 |
79 |
80 | // check if there is a error after kernel execution
81 | #define CUDA_POST_KERNEL_CHECK \
82 | CUDA_CHECK(cudaPeekAtLastError()); \
83 | CUDA_CHECK(cudaGetLastError());
84 |
85 | // CUDA: grid stride looping
86 | #define CUDA_KERNEL_LOOP(i, n) \
87 | for (int i = blockIdx.x * blockDim.x + threadIdx.x; \
88 | i < (n); \
89 | i += blockDim.x * gridDim.x)
90 |
91 | // Use 1024 threads per block, which requires cuda sm_2x or above
92 | const int CUDA_NUM_THREADS = 1024;
93 |
94 | // CUDA: number of blocks for threads.
95 | inline int GET_BLOCKS_T(const int N, const int N_THREADS) {
96 | return (N + N_THREADS - 1) / N_THREADS;
97 | }
98 | inline int GET_BLOCKS(const int N) {
99 | return GET_BLOCKS_T(N, CUDA_NUM_THREADS);
100 | // return (N + CUDA_NUM_THREADS - 1) / CUDA_NUM_THREADS;
101 | }
102 |
103 | template
104 | T* device_malloc(long N) {
105 | T* dptr;
106 | CUDA_CHECK(cudaMalloc(&dptr, N * sizeof(T)));
107 | if(DEBUG) { printf("[DEBUG] device_malloc %p, %ld\n", dptr, N); }
108 | return dptr;
109 | }
110 |
111 | template
112 | void device_free(T* dptr) {
113 | if(DEBUG) { printf("[DEBUG] device_free %p\n", dptr); }
114 | CUDA_CHECK(cudaFree(dptr));
115 | }
116 |
117 | template
118 | void host_to_device(const T* hptr, T* dptr, long N) {
119 | if(DEBUG) { printf("[DEBUG] host_to_device %p => %p, %ld\n", hptr, dptr, N); }
120 | CUDA_CHECK(cudaMemcpy(dptr, hptr, N * sizeof(T), cudaMemcpyHostToDevice));
121 | }
122 |
123 | template
124 | T* host_to_device_malloc(const T* hptr, long N) {
125 | T* dptr = device_malloc(N);
126 | host_to_device(hptr, dptr, N);
127 | return dptr;
128 | }
129 |
130 | template
131 | void device_to_host(const T* dptr, T* hptr, long N) {
132 | if(DEBUG) { printf("[DEBUG] device_to_host %p => %p, %ld\n", dptr, hptr, N); }
133 | CUDA_CHECK(cudaMemcpy(hptr, dptr, N * sizeof(T), cudaMemcpyDeviceToHost));
134 | }
135 |
136 | template
137 | T* device_to_host_malloc(const T* dptr, long N) {
138 | T* hptr = new T[N];
139 | device_to_host(dptr, hptr, N);
140 | return hptr;
141 | }
142 |
143 | template
144 | void device_to_device(const T* dptr, T* hptr, long N) {
145 | if(DEBUG) { printf("[DEBUG] device_to_device %p => %p, %ld\n", dptr, hptr, N); }
146 | CUDA_CHECK(cudaMemcpy(hptr, dptr, N * sizeof(T), cudaMemcpyDeviceToDevice));
147 | }
148 |
149 |
150 |
151 | inline void views_to_gpu(const Views& views_cpu, Views& views_gpu, bool alloc) {
152 | views_gpu.n_views_ = views_cpu.n_views_;
153 | views_gpu.rows_ = views_cpu.rows_;
154 | views_gpu.cols_ = views_cpu.cols_;
155 |
156 | printf(" views_to_gpu with %dx%dx%d\n", views_gpu.n_views_, views_gpu.rows_, views_gpu.cols_);
157 | int N = views_cpu.n_views_ * views_cpu.rows_ * views_cpu.cols_;
158 | if(alloc) {
159 | views_gpu.depthmaps_ = device_malloc(N);
160 | }
161 | host_to_device(views_cpu.depthmaps_, views_gpu.depthmaps_, N);
162 |
163 | N = views_cpu.n_views_ * 3 * 3;
164 | if(alloc) {
165 | views_gpu.Ks_ = device_malloc(N);
166 | }
167 | host_to_device(views_cpu.Ks_, views_gpu.Ks_, N);
168 |
169 | N = views_cpu.n_views_ * 3 * 3;
170 | if(alloc) {
171 | views_gpu.Rs_ = device_malloc(N);
172 | }
173 | host_to_device(views_cpu.Rs_, views_gpu.Rs_, N);
174 |
175 | N = views_cpu.n_views_ * 3;
176 | if(alloc) {
177 | views_gpu.Ts_ = device_malloc(N);
178 | }
179 | host_to_device(views_cpu.Ts_, views_gpu.Ts_ , N);
180 | }
181 |
182 | inline void views_free_gpu(Views& views_gpu) {
183 | device_free(views_gpu.depthmaps_);
184 | device_free(views_gpu.Ks_);
185 | device_free(views_gpu.Rs_);
186 | device_free(views_gpu.Ts_);
187 | }
188 |
189 |
190 |
191 | inline void volume_alloc_like_gpu(const Volume& vol_cpu, Volume& vol_gpu) {
192 | vol_gpu.channels_ = vol_cpu.channels_;
193 | vol_gpu.depth_ = vol_cpu.depth_;
194 | vol_gpu.height_ = vol_cpu.height_;
195 | vol_gpu.width_ = vol_cpu.width_;
196 | int N = vol_cpu.channels_ * vol_cpu.depth_ * vol_cpu.height_ * vol_cpu.width_;
197 | printf(" volume_alloc_like_gpu gpu memory for volume %dx%dx%dx%d\n", vol_gpu.channels_, vol_gpu.depth_, vol_gpu.height_, vol_gpu.width_);
198 | vol_gpu.data_ = device_malloc(N);
199 | }
200 |
201 | inline void volume_alloc_gpu(int channels, int depth, int height, int width, Volume& vol_gpu) {
202 | vol_gpu.channels_ = channels;
203 | vol_gpu.depth_ = depth;
204 | vol_gpu.height_ = height;
205 | vol_gpu.width_ = width;
206 | int N = channels * depth * height * width;
207 | printf(" volume_alloc_gpu gpu memory for volume %dx%dx%dx%d\n", vol_gpu.channels_, vol_gpu.depth_, vol_gpu.height_, vol_gpu.width_);
208 | vol_gpu.data_ = device_malloc(N);
209 | }
210 |
211 | inline void volume_fill_data_gpu(Volume& vol, float fill_value) {
212 | int n = vol.channels_ * vol.depth_ * vol.height_ * vol.width_;
213 | thrust::fill_n(thrust::device, vol.data_, n, fill_value);
214 | }
215 |
216 | template
217 | struct scalar_multiply {
218 | T s;
219 | scalar_multiply(T _s) : s(_s) {}
220 |
221 | __host__ __device__ T operator()(T& x) const {
222 | return x * s;
223 | }
224 | };
225 |
226 | inline void volume_mul_data_gpu(Volume& vol, float val) {
227 | int n = vol.channels_ * vol.depth_ * vol.height_ * vol.width_;
228 | thrust::transform(thrust::device, vol.data_, vol.data_ + n, vol.data_, scalar_multiply(val));
229 | }
230 |
231 | inline void volume_to_gpu(const Volume& vol_cpu, Volume& vol_gpu, bool alloc) {
232 | vol_gpu.channels_ = vol_cpu.channels_;
233 | vol_gpu.depth_ = vol_cpu.depth_;
234 | vol_gpu.height_ = vol_cpu.height_;
235 | vol_gpu.width_ = vol_cpu.width_;
236 |
237 | int N = vol_cpu.channels_ * vol_cpu.depth_ * vol_cpu.height_ * vol_cpu.width_;
238 | if(alloc) {
239 | vol_gpu.data_ = device_malloc(N);
240 | }
241 | host_to_device(vol_cpu.data_, vol_gpu.data_, N);
242 | }
243 |
244 | inline void volume_to_cpu(const Volume& vol_gpu, Volume& vol_cpu, bool alloc) {
245 | vol_cpu.channels_ = vol_gpu.channels_;
246 | vol_cpu.depth_ = vol_gpu.depth_;
247 | vol_cpu.height_ = vol_gpu.height_;
248 | vol_cpu.width_ = vol_gpu.width_;
249 |
250 | int N = vol_gpu.channels_ * vol_gpu.depth_ * vol_gpu.height_ * vol_gpu.width_;
251 | if(alloc) {
252 | vol_cpu.data_ = new float[N];
253 | }
254 | device_to_host(vol_gpu.data_, vol_cpu.data_, N);
255 | }
256 |
257 | inline void volume_free_gpu(Volume& vol_gpu) {
258 | device_free(vol_gpu.data_);
259 | }
260 |
261 |
262 |
263 |
264 | template
265 | __global__ void kernel_fusion(int vx_res3, const Views views, const FusionFunctorT functor, float vx_size, Volume vol);
266 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libmcubes/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012-2015, P. M. Neila
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of the copyright holder nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libmcubes/README.rst:
--------------------------------------------------------------------------------
1 | ========
2 | PyMCubes
3 | ========
4 |
5 | PyMCubes is an implementation of the marching cubes algorithm to extract
6 | isosurfaces from volumetric data. The volumetric data can be given as a
7 | three-dimensional NumPy array or as a Python function ``f(x, y, z)``. The first
8 | option is much faster, but it requires more memory and becomes unfeasible for
9 | very large volumes.
10 |
11 | PyMCubes also provides a function to export the results of the marching cubes as
12 | COLLADA ``(.dae)`` files. This requires the
13 | `PyCollada `_ library.
14 |
15 | Installation
16 | ============
17 |
18 | Just as any standard Python package, clone or download the project
19 | and run::
20 |
21 | $ cd path/to/PyMCubes
22 | $ python setup.py build
23 | $ python setup.py install
24 |
25 | If you do not have write permission on the directory of Python packages,
26 | install with the ``--user`` option::
27 |
28 | $ python setup.py install --user
29 |
30 | Example
31 | =======
32 |
33 | The following example creates a data volume with spherical isosurfaces and
34 | extracts one of them (i.e., a sphere) with PyMCubes. The result is exported as
35 | ``sphere.dae``::
36 |
37 | >>> import numpy as np
38 | >>> import mcubes
39 |
40 | # Create a data volume (30 x 30 x 30)
41 | >>> X, Y, Z = np.mgrid[:30, :30, :30]
42 | >>> u = (X-15)**2 + (Y-15)**2 + (Z-15)**2 - 8**2
43 |
44 | # Extract the 0-isosurface
45 | >>> vertices, triangles = mcubes.marching_cubes(u, 0)
46 |
47 | # Export the result to sphere.dae
48 | >>> mcubes.export_mesh(vertices, triangles, "sphere.dae", "MySphere")
49 |
50 | The second example is very similar to the first one, but it uses a function
51 | to represent the volume instead of a NumPy array::
52 |
53 | >>> import numpy as np
54 | >>> import mcubes
55 |
56 | # Create the volume
57 | >>> f = lambda x, y, z: x**2 + y**2 + z**2
58 |
59 | # Extract the 16-isosurface
60 | >>> vertices, triangles = mcubes.marching_cubes_func((-10,-10,-10), (10,10,10),
61 | ... 100, 100, 100, f, 16)
62 |
63 | # Export the result to sphere2.dae
64 | >>> mcubes.export_mesh(vertices, triangles, "sphere2.dae", "MySphere")
65 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libmcubes/__init__.py:
--------------------------------------------------------------------------------
1 | from .mcubes import marching_cubes, marching_cubes_func
2 | from .exporter import export_mesh, export_obj, export_off
3 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libmcubes/exporter.py:
--------------------------------------------------------------------------------
1 |
2 | import numpy as np
3 |
4 |
5 | def export_obj(vertices, triangles, filename):
6 | """
7 | Exports a mesh in the (.obj) format.
8 | """
9 |
10 | with open(filename, 'w') as fh:
11 |
12 | for v in vertices:
13 | fh.write("v {} {} {}\n".format(*v))
14 |
15 | for f in triangles:
16 | fh.write("f {} {} {}\n".format(*(f + 1)))
17 |
18 |
19 | def export_off(vertices, triangles, filename):
20 | """
21 | Exports a mesh in the (.off) format.
22 | """
23 |
24 | with open(filename, 'w') as fh:
25 | fh.write('OFF\n')
26 | fh.write('{} {} 0\n'.format(len(vertices), len(triangles)))
27 |
28 | for v in vertices:
29 | fh.write("{} {} {}\n".format(*v))
30 |
31 | for f in triangles:
32 | fh.write("3 {} {} {}\n".format(*f))
33 |
34 |
35 | def export_mesh(vertices, triangles, filename, mesh_name="mcubes_mesh"):
36 | """
37 | Exports a mesh in the COLLADA (.dae) format.
38 |
39 | Needs PyCollada (https://github.com/pycollada/pycollada).
40 | """
41 |
42 | import collada
43 |
44 | mesh = collada.Collada()
45 |
46 | vert_src = collada.source.FloatSource("verts-array", vertices, ('X','Y','Z'))
47 | geom = collada.geometry.Geometry(mesh, "geometry0", mesh_name, [vert_src])
48 |
49 | input_list = collada.source.InputList()
50 | input_list.addInput(0, 'VERTEX', "#verts-array")
51 |
52 | triset = geom.createTriangleSet(np.copy(triangles), input_list, "")
53 | geom.primitives.append(triset)
54 | mesh.geometries.append(geom)
55 |
56 | geomnode = collada.scene.GeometryNode(geom, [])
57 | node = collada.scene.Node(mesh_name, children=[geomnode])
58 |
59 | myscene = collada.scene.Scene("mcubes_scene", [node])
60 | mesh.scenes.append(myscene)
61 | mesh.scene = myscene
62 |
63 | mesh.write(filename)
64 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libmcubes/marchingcubes.cpp:
--------------------------------------------------------------------------------
1 |
2 | #include "marchingcubes.h"
3 |
4 | namespace mc
5 | {
6 |
7 | int edge_table[256] =
8 | {
9 | 0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
10 | 0x190, 0x099, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
11 | 0x230, 0x339, 0x033, 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
12 | 0x3a0, 0x2a9, 0x1a3, 0x0aa, 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
13 | 0x460, 0x569, 0x663, 0x76a, 0x066, 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
14 | 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0x0ff, 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
15 | 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x055, 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
16 | 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0x0cc, 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
17 | 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0x0cc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
18 | 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x055, 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
19 | 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0x0ff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
20 | 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x066, 0x76a, 0x663, 0x569, 0x460,
21 | 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0x0aa, 0x1a3, 0x2a9, 0x3a0,
22 | 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x033, 0x339, 0x230,
23 | 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x099, 0x190,
24 | 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000
25 | };
26 |
27 | int triangle_table[256][16] =
28 | {
29 | {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
30 | {0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
31 | {0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
32 | {1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
33 | {1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
34 | {0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
35 | {9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
36 | {2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1},
37 | {3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
38 | {0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
39 | {1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
40 | {1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1},
41 | {3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
42 | {0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1},
43 | {3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1},
44 | {9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
45 | {4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
46 | {4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
47 | {0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
48 | {4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1},
49 | {1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
50 | {3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1},
51 | {9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1},
52 | {2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1},
53 | {8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
54 | {11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1},
55 | {9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1},
56 | {4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1},
57 | {3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1},
58 | {1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1},
59 | {4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1},
60 | {4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1},
61 | {9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
62 | {9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
63 | {0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
64 | {8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1},
65 | {1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
66 | {3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},
67 | {5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1},
68 | {2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1},
69 | {9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
70 | {0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},
71 | {0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1},
72 | {2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1},
73 | {10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1},
74 | {4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1},
75 | {5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1},
76 | {5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1},
77 | {9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
78 | {9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1},
79 | {0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1},
80 | {1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
81 | {9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1},
82 | {10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1},
83 | {8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1},
84 | {2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1},
85 | {7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1},
86 | {9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1},
87 | {2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1},
88 | {11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1},
89 | {9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1},
90 | {5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1},
91 | {11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1},
92 | {11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
93 | {10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
94 | {0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
95 | {9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
96 | {1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},
97 | {1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
98 | {1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1},
99 | {9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1},
100 | {5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1},
101 | {2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
102 | {11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},
103 | {0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},
104 | {5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1},
105 | {6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1},
106 | {0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1},
107 | {3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1},
108 | {6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1},
109 | {5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
110 | {4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1},
111 | {1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1},
112 | {10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1},
113 | {6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1},
114 | {1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1},
115 | {8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1},
116 | {7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1},
117 | {3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},
118 | {5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1},
119 | {0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1},
120 | {9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1},
121 | {8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1},
122 | {5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1},
123 | {0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1},
124 | {6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1},
125 | {10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
126 | {4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1},
127 | {10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1},
128 | {8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1},
129 | {1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1},
130 | {3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1},
131 | {0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
132 | {8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1},
133 | {10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1},
134 | {0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1},
135 | {3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1},
136 | {6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1},
137 | {9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1},
138 | {8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1},
139 | {3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1},
140 | {6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
141 | {7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1},
142 | {0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1},
143 | {10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1},
144 | {10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1},
145 | {1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1},
146 | {2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1},
147 | {7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1},
148 | {7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
149 | {2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1},
150 | {2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1},
151 | {1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1},
152 | {11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1},
153 | {8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1},
154 | {0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
155 | {7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1},
156 | {7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
157 | {7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
158 | {3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
159 | {0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
160 | {8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},
161 | {10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
162 | {1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},
163 | {2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},
164 | {6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1},
165 | {7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
166 | {7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1},
167 | {2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1},
168 | {1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1},
169 | {10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1},
170 | {10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1},
171 | {0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1},
172 | {7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1},
173 | {6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
174 | {3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1},
175 | {8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1},
176 | {9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1},
177 | {6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1},
178 | {1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1},
179 | {4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1},
180 | {10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1},
181 | {8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1},
182 | {0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
183 | {1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1},
184 | {1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1},
185 | {8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1},
186 | {10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1},
187 | {4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1},
188 | {10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
189 | {4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
190 | {0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},
191 | {5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1},
192 | {11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1},
193 | {9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1},
194 | {6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1},
195 | {7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1},
196 | {3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1},
197 | {7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1},
198 | {9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1},
199 | {3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1},
200 | {6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1},
201 | {9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1},
202 | {1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1},
203 | {4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1},
204 | {7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1},
205 | {6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1},
206 | {3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1},
207 | {0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1},
208 | {6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1},
209 | {1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1},
210 | {0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1},
211 | {11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1},
212 | {6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1},
213 | {5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1},
214 | {9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1},
215 | {1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1},
216 | {1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
217 | {1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1},
218 | {10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1},
219 | {0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
220 | {10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
221 | {11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
222 | {11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1},
223 | {5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1},
224 | {10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1},
225 | {11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1},
226 | {0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1},
227 | {9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1},
228 | {7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1},
229 | {2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1},
230 | {8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1},
231 | {9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1},
232 | {9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1},
233 | {1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
234 | {0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1},
235 | {9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1},
236 | {9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
237 | {5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1},
238 | {5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1},
239 | {0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1},
240 | {10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1},
241 | {2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1},
242 | {0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1},
243 | {0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1},
244 | {9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
245 | {2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1},
246 | {5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1},
247 | {3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1},
248 | {5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1},
249 | {8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1},
250 | {0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
251 | {8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1},
252 | {9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
253 | {4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1},
254 | {0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1},
255 | {1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1},
256 | {3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1},
257 | {4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1},
258 | {9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1},
259 | {11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1},
260 | {11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1},
261 | {2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1},
262 | {9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1},
263 | {3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1},
264 | {1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
265 | {4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1},
266 | {4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1},
267 | {4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
268 | {4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
269 | {9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
270 | {3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1},
271 | {0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1},
272 | {3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
273 | {1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1},
274 | {3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1},
275 | {0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
276 | {3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
277 | {2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1},
278 | {9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
279 | {2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1},
280 | {1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
281 | {1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
282 | {0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
283 | {0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
284 | {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}
285 | };
286 |
287 | namespace private_
288 | {
289 |
290 | double mc_isovalue_interpolation(double isovalue, double f1, double f2,
291 | double x1, double x2)
292 | {
293 | if(f2==f1)
294 | return (x2+x1)/2;
295 |
296 | return (x2-x1)*(isovalue-f1)/(f2-f1) + x1;
297 | }
298 |
299 | void mc_add_vertex(double x1, double y1, double z1, double c2,
300 | int axis, double f1, double f2, double isovalue, std::vector* vertices)
301 | {
302 | if(axis == 0)
303 | {
304 | double x = mc_isovalue_interpolation(isovalue, f1, f2, x1, c2);
305 | vertices->push_back(x);
306 | vertices->push_back(y1);
307 | vertices->push_back(z1);
308 | return;
309 | }
310 | if(axis == 1)
311 | {
312 | double y = mc_isovalue_interpolation(isovalue, f1, f2, y1, c2);
313 | vertices->push_back(x1);
314 | vertices->push_back(y);
315 | vertices->push_back(z1);
316 | return;
317 | }
318 | if(axis == 2)
319 | {
320 | double z = mc_isovalue_interpolation(isovalue, f1, f2, z1, c2);
321 | vertices->push_back(x1);
322 | vertices->push_back(y1);
323 | vertices->push_back(z);
324 | return;
325 | }
326 | }
327 |
328 | }
329 |
330 | }
331 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libmcubes/marchingcubes.h:
--------------------------------------------------------------------------------
1 |
2 | #ifndef _MARCHING_CUBES_H
3 | #define _MARCHING_CUBES_H
4 |
5 | #include
6 | #include
7 |
8 | namespace mc
9 | {
10 |
11 | extern int edge_table[256];
12 | extern int triangle_table[256][16];
13 |
14 | namespace private_
15 | {
16 |
17 | double mc_isovalue_interpolation(double isovalue, double f1, double f2,
18 | double x1, double x2);
19 | void mc_add_vertex(double x1, double y1, double z1, double c2,
20 | int axis, double f1, double f2, double isovalue, std::vector* vertices);
21 | }
22 |
23 | template
24 | void marching_cubes(const vector3& lower, const vector3& upper,
25 | int numx, int numy, int numz, formula f, double isovalue,
26 | std::vector& vertices, std::vector& polygons)
27 | {
28 | using namespace private_;
29 |
30 | // typedef decltype(lower[0]) coord_type;
31 |
32 | // numx, numy and numz are the numbers of evaluations in each direction
33 | --numx; --numy; --numz;
34 |
35 | coord_type dx = (upper[0] - lower[0])/static_cast(numx);
36 | coord_type dy = (upper[1] - lower[1])/static_cast(numy);
37 | coord_type dz = (upper[2] - lower[2])/static_cast(numz);
38 |
39 | size_t* shared_indices = new size_t[2*numy*numz*3];
40 | const int z3 = numz*3;
41 | const int yz3 = numy*z3;
42 |
43 | for(int i=0; i indices(12, -1);
74 | if(edges & 0x040)
75 | {
76 | indices[6] = vertices.size() / 3;
77 | shared_indices[i_mod_2*yz3 + j*z3 + k*3 + 0] = indices[6];
78 | mc_add_vertex(x_dx, y_dy, z_dz, x, 0, v[6], v[7], isovalue, &vertices);
79 | }
80 | if(edges & 0x020)
81 | {
82 | indices[5] = vertices.size() / 3;
83 | shared_indices[i_mod_2*yz3 + j*z3 + k*3 + 1] = indices[5];
84 | mc_add_vertex(x_dx, y, z_dz, y_dy, 1, v[5], v[6], isovalue, &vertices);
85 | }
86 | if(edges & 0x400)
87 | {
88 | indices[10] = vertices.size() / 3;
89 | shared_indices[i_mod_2*yz3 + j*z3 + k*3 + 2] = indices[10];
90 | mc_add_vertex(x_dx, y+dx, z, z_dz, 2, v[2], v[6], isovalue, &vertices);
91 | }
92 |
93 | if(edges & 0x001)
94 | {
95 | if(j == 0 || k == 0)
96 | {
97 | indices[0] = vertices.size() / 3;
98 | mc_add_vertex(x, y, z, x_dx, 0, v[0], v[1], isovalue, &vertices);
99 | }
100 | else
101 | indices[0] = shared_indices[i_mod_2*yz3 + (j-1)*z3 + (k-1)*3 + 0];
102 | }
103 | if(edges & 0x002)
104 | {
105 | if(k == 0)
106 | {
107 | indices[1] = vertices.size() / 3;
108 | mc_add_vertex(x_dx, y, z, y_dy, 1, v[1], v[2], isovalue, &vertices);
109 | }
110 | else
111 | indices[1] = shared_indices[i_mod_2*yz3 + j*z3 + (k-1)*3 + 1];
112 | }
113 | if(edges & 0x004)
114 | {
115 | if(k == 0)
116 | {
117 | indices[2] = vertices.size() / 3;
118 | mc_add_vertex(x_dx, y_dy, z, x, 0, v[2], v[3], isovalue, &vertices);
119 | }
120 | else
121 | indices[2] = shared_indices[i_mod_2*yz3 + j*z3 + (k-1)*3 + 0];
122 | }
123 | if(edges & 0x008)
124 | {
125 | if(i == 0 || k == 0)
126 | {
127 | indices[3] = vertices.size() / 3;
128 | mc_add_vertex(x, y_dy, z, y, 1, v[3], v[0], isovalue, &vertices);
129 | }
130 | else
131 | indices[3] = shared_indices[i_mod_2_inv*yz3 + j*z3 + (k-1)*3 + 1];
132 | }
133 | if(edges & 0x010)
134 | {
135 | if(j == 0)
136 | {
137 | indices[4] = vertices.size() / 3;
138 | mc_add_vertex(x, y, z_dz, x_dx, 0, v[4], v[5], isovalue, &vertices);
139 | }
140 | else
141 | indices[4] = shared_indices[i_mod_2*yz3 + (j-1)*z3 + k*3 + 0];
142 | }
143 | if(edges & 0x080)
144 | {
145 | if(i == 0)
146 | {
147 | indices[7] = vertices.size() / 3;
148 | mc_add_vertex(x, y_dy, z_dz, y, 1, v[7], v[4], isovalue, &vertices);
149 | }
150 | else
151 | indices[7] = shared_indices[i_mod_2_inv*yz3 + j*z3 + k*3 + 1];
152 | }
153 | if(edges & 0x100)
154 | {
155 | if(i == 0 || j == 0)
156 | {
157 | indices[8] = vertices.size() / 3;
158 | mc_add_vertex(x, y, z, z_dz, 2, v[0], v[4], isovalue, &vertices);
159 | }
160 | else
161 | indices[8] = shared_indices[i_mod_2_inv*yz3 + (j-1)*z3 + k*3 + 2];
162 | }
163 | if(edges & 0x200)
164 | {
165 | if(j == 0)
166 | {
167 | indices[9] = vertices.size() / 3;
168 | mc_add_vertex(x_dx, y, z, z_dz, 2, v[1], v[5], isovalue, &vertices);
169 | }
170 | else
171 | indices[9] = shared_indices[i_mod_2*yz3 + (j-1)*z3 + k*3 + 2];
172 | }
173 | if(edges & 0x800)
174 | {
175 | if(i == 0)
176 | {
177 | indices[11] = vertices.size() / 3;
178 | mc_add_vertex(x, y_dy, z, z_dz, 2, v[3], v[7], isovalue, &vertices);
179 | }
180 | else
181 | indices[11] = shared_indices[i_mod_2_inv*yz3 + j*z3 + k*3 + 2];
182 | }
183 |
184 | int tri;
185 | int* triangle_table_ptr = triangle_table[cubeindex];
186 | for(int m=0; tri = triangle_table_ptr[m], tri != -1; ++m)
187 | polygons.push_back(indices[tri]);
188 | }
189 | }
190 | }
191 |
192 | delete [] shared_indices;
193 | }
194 |
195 | template
196 | void marching_cubes2(const vector3& lower, const vector3& upper,
197 | int numx, int numy, int numz, formula f, double isovalue,
198 | std::vector& vertices, std::vector& polygons)
199 | {
200 | using namespace private_;
201 |
202 | // typedef decltype(lower[0]) coord_type;
203 |
204 | // numx, numy and numz are the numbers of evaluations in each direction
205 | --numx; --numy; --numz;
206 |
207 | coord_type dx = (upper[0] - lower[0])/static_cast(numx);
208 | coord_type dy = (upper[1] - lower[1])/static_cast(numy);
209 | coord_type dz = (upper[2] - lower[2])/static_cast(numz);
210 |
211 | size_t* shared_indices = new size_t[2*numy*numz*3];
212 | const int z3 = numz*3;
213 | const int yz3 = numy*z3;
214 |
215 | for(int i=0; i indices(12, -1);
246 | if(edges & 0x040)
247 | {
248 | indices[6] = vertices.size() / 3;
249 | shared_indices[i_mod_2*yz3 + j*z3 + k*3 + 0] = indices[6];
250 | mc_add_vertex(x_dx, y_dy, z_dz, x, 0, v[6], v[7], isovalue, &vertices);
251 | }
252 | if(edges & 0x020)
253 | {
254 | indices[5] = vertices.size() / 3;
255 | shared_indices[i_mod_2*yz3 + j*z3 + k*3 + 1] = indices[5];
256 | mc_add_vertex(x_dx, y, z_dz, y_dy, 1, v[5], v[6], isovalue, &vertices);
257 | }
258 | if(edges & 0x400)
259 | {
260 | indices[10] = vertices.size() / 3;
261 | shared_indices[i_mod_2*yz3 + j*z3 + k*3 + 2] = indices[10];
262 | mc_add_vertex(x_dx, y+dx, z, z_dz, 2, v[2], v[6], isovalue, &vertices);
263 | }
264 |
265 | if(edges & 0x001)
266 | {
267 | if(j == 0 || k == 0)
268 | {
269 | indices[0] = vertices.size() / 3;
270 | mc_add_vertex(x, y, z, x_dx, 0, v[0], v[1], isovalue, &vertices);
271 | }
272 | else
273 | indices[0] = shared_indices[i_mod_2*yz3 + (j-1)*z3 + (k-1)*3 + 0];
274 | }
275 | if(edges & 0x002)
276 | {
277 | if(k == 0)
278 | {
279 | indices[1] = vertices.size() / 3;
280 | mc_add_vertex(x_dx, y, z, y_dy, 1, v[1], v[2], isovalue, &vertices);
281 | }
282 | else
283 | indices[1] = shared_indices[i_mod_2*yz3 + j*z3 + (k-1)*3 + 1];
284 | }
285 | if(edges & 0x004)
286 | {
287 | if(k == 0)
288 | {
289 | indices[2] = vertices.size() / 3;
290 | mc_add_vertex(x_dx, y_dy, z, x, 0, v[2], v[3], isovalue, &vertices);
291 | }
292 | else
293 | indices[2] = shared_indices[i_mod_2*yz3 + j*z3 + (k-1)*3 + 0];
294 | }
295 | if(edges & 0x008)
296 | {
297 | if(i == 0 || k == 0)
298 | {
299 | indices[3] = vertices.size() / 3;
300 | mc_add_vertex(x, y_dy, z, y, 1, v[3], v[0], isovalue, &vertices);
301 | }
302 | else
303 | indices[3] = shared_indices[i_mod_2_inv*yz3 + j*z3 + (k-1)*3 + 1];
304 | }
305 | if(edges & 0x010)
306 | {
307 | if(j == 0)
308 | {
309 | indices[4] = vertices.size() / 3;
310 | mc_add_vertex(x, y, z_dz, x_dx, 0, v[4], v[5], isovalue, &vertices);
311 | }
312 | else
313 | indices[4] = shared_indices[i_mod_2*yz3 + (j-1)*z3 + k*3 + 0];
314 | }
315 | if(edges & 0x080)
316 | {
317 | if(i == 0)
318 | {
319 | indices[7] = vertices.size() / 3;
320 | mc_add_vertex(x, y_dy, z_dz, y, 1, v[7], v[4], isovalue, &vertices);
321 | }
322 | else
323 | indices[7] = shared_indices[i_mod_2_inv*yz3 + j*z3 + k*3 + 1];
324 | }
325 | if(edges & 0x100)
326 | {
327 | if(i == 0 || j == 0)
328 | {
329 | indices[8] = vertices.size() / 3;
330 | mc_add_vertex(x, y, z, z_dz, 2, v[0], v[4], isovalue, &vertices);
331 | }
332 | else
333 | indices[8] = shared_indices[i_mod_2_inv*yz3 + (j-1)*z3 + k*3 + 2];
334 | }
335 | if(edges & 0x200)
336 | {
337 | if(j == 0)
338 | {
339 | indices[9] = vertices.size() / 3;
340 | mc_add_vertex(x_dx, y, z, z_dz, 2, v[1], v[5], isovalue, &vertices);
341 | }
342 | else
343 | indices[9] = shared_indices[i_mod_2*yz3 + (j-1)*z3 + k*3 + 2];
344 | }
345 | if(edges & 0x800)
346 | {
347 | if(i == 0)
348 | {
349 | indices[11] = vertices.size() / 3;
350 | mc_add_vertex(x, y_dy, z, z_dz, 2, v[3], v[7], isovalue, &vertices);
351 | }
352 | else
353 | indices[11] = shared_indices[i_mod_2_inv*yz3 + j*z3 + k*3 + 2];
354 | }
355 |
356 | int tri;
357 | int* triangle_table_ptr = triangle_table[cubeindex];
358 | for(int m=0; tri = triangle_table_ptr[m], tri != -1; ++m)
359 | polygons.push_back(indices[tri]);
360 | }
361 | }
362 | }
363 |
364 | delete [] shared_indices;
365 | }
366 |
367 | template
368 | void marching_cubes3(const vector3& lower, const vector3& upper,
369 | int numx, int numy, int numz, formula f, double isovalue,
370 | std::vector& vertices, std::vector& polygons)
371 | {
372 | using namespace private_;
373 |
374 | // typedef decltype(lower[0]) coord_type;
375 |
376 | // numx, numy and numz are the numbers of evaluations in each direction
377 | --numx; --numy; --numz;
378 |
379 | coord_type dx = (upper[0] - lower[0])/static_cast(numx);
380 | coord_type dy = (upper[1] - lower[1])/static_cast(numy);
381 | coord_type dz = (upper[2] - lower[2])/static_cast(numz);
382 |
383 | size_t* shared_indices = new size_t[2*numy*numz*3];
384 | const int z3 = numz*3;
385 | const int yz3 = numy*z3;
386 |
387 | for(int i=0; i indices(12, -1);
418 | if(edges & 0x040)
419 | {
420 | indices[6] = vertices.size() / 3;
421 | shared_indices[i_mod_2*yz3 + j*z3 + k*3 + 0] = indices[6];
422 | mc_add_vertex(x_dx, y_dy, z_dz, x, 0, v[6], v[7], isovalue, &vertices);
423 | }
424 | if(edges & 0x020)
425 | {
426 | indices[5] = vertices.size() / 3;
427 | shared_indices[i_mod_2*yz3 + j*z3 + k*3 + 1] = indices[5];
428 | mc_add_vertex(x_dx, y, z_dz, y_dy, 1, v[5], v[6], isovalue, &vertices);
429 | }
430 | if(edges & 0x400)
431 | {
432 | indices[10] = vertices.size() / 3;
433 | shared_indices[i_mod_2*yz3 + j*z3 + k*3 + 2] = indices[10];
434 | mc_add_vertex(x_dx, y+dx, z, z_dz, 2, v[2], v[6], isovalue, &vertices);
435 | }
436 |
437 | if(edges & 0x001)
438 | {
439 | if(j == 0 || k == 0)
440 | {
441 | indices[0] = vertices.size() / 3;
442 | mc_add_vertex(x, y, z, x_dx, 0, v[0], v[1], isovalue, &vertices);
443 | }
444 | else
445 | indices[0] = shared_indices[i_mod_2*yz3 + (j-1)*z3 + (k-1)*3 + 0];
446 | }
447 | if(edges & 0x002)
448 | {
449 | if(k == 0)
450 | {
451 | indices[1] = vertices.size() / 3;
452 | mc_add_vertex(x_dx, y, z, y_dy, 1, v[1], v[2], isovalue, &vertices);
453 | }
454 | else
455 | indices[1] = shared_indices[i_mod_2*yz3 + j*z3 + (k-1)*3 + 1];
456 | }
457 | if(edges & 0x004)
458 | {
459 | if(k == 0)
460 | {
461 | indices[2] = vertices.size() / 3;
462 | mc_add_vertex(x_dx, y_dy, z, x, 0, v[2], v[3], isovalue, &vertices);
463 | }
464 | else
465 | indices[2] = shared_indices[i_mod_2*yz3 + j*z3 + (k-1)*3 + 0];
466 | }
467 | if(edges & 0x008)
468 | {
469 | if(i == 0 || k == 0)
470 | {
471 | indices[3] = vertices.size() / 3;
472 | mc_add_vertex(x, y_dy, z, y, 1, v[3], v[0], isovalue, &vertices);
473 | }
474 | else
475 | indices[3] = shared_indices[i_mod_2_inv*yz3 + j*z3 + (k-1)*3 + 1];
476 | }
477 | if(edges & 0x010)
478 | {
479 | if(j == 0)
480 | {
481 | indices[4] = vertices.size() / 3;
482 | mc_add_vertex(x, y, z_dz, x_dx, 0, v[4], v[5], isovalue, &vertices);
483 | }
484 | else
485 | indices[4] = shared_indices[i_mod_2*yz3 + (j-1)*z3 + k*3 + 0];
486 | }
487 | if(edges & 0x080)
488 | {
489 | if(i == 0)
490 | {
491 | indices[7] = vertices.size() / 3;
492 | mc_add_vertex(x, y_dy, z_dz, y, 1, v[7], v[4], isovalue, &vertices);
493 | }
494 | else
495 | indices[7] = shared_indices[i_mod_2_inv*yz3 + j*z3 + k*3 + 1];
496 | }
497 | if(edges & 0x100)
498 | {
499 | if(i == 0 || j == 0)
500 | {
501 | indices[8] = vertices.size() / 3;
502 | mc_add_vertex(x, y, z, z_dz, 2, v[0], v[4], isovalue, &vertices);
503 | }
504 | else
505 | indices[8] = shared_indices[i_mod_2_inv*yz3 + (j-1)*z3 + k*3 + 2];
506 | }
507 | if(edges & 0x200)
508 | {
509 | if(j == 0)
510 | {
511 | indices[9] = vertices.size() / 3;
512 | mc_add_vertex(x_dx, y, z, z_dz, 2, v[1], v[5], isovalue, &vertices);
513 | }
514 | else
515 | indices[9] = shared_indices[i_mod_2*yz3 + (j-1)*z3 + k*3 + 2];
516 | }
517 | if(edges & 0x800)
518 | {
519 | if(i == 0)
520 | {
521 | indices[11] = vertices.size() / 3;
522 | mc_add_vertex(x, y_dy, z, z_dz, 2, v[3], v[7], isovalue, &vertices);
523 | }
524 | else
525 | indices[11] = shared_indices[i_mod_2_inv*yz3 + j*z3 + k*3 + 2];
526 | }
527 |
528 | int tri;
529 | int* triangle_table_ptr = triangle_table[cubeindex];
530 | for(int m=0; tri = triangle_table_ptr[m], tri != -1; ++m)
531 | polygons.push_back(indices[tri]);
532 | }
533 | }
534 | }
535 |
536 | delete [] shared_indices;
537 | }
538 |
539 | }
540 |
541 | #endif // _MARCHING_CUBES_H
542 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libmcubes/mcubes.pyx:
--------------------------------------------------------------------------------
1 |
2 | # distutils: language = c++
3 | # cython: embedsignature = True
4 |
5 | # from libcpp.vector cimport vector
6 | import numpy as np
7 |
8 | # Define PY_ARRAY_UNIQUE_SYMBOL
9 | cdef extern from "pyarray_symbol.h":
10 | pass
11 |
12 | cimport numpy as np
13 |
14 | np.import_array()
15 |
16 | cdef extern from "pywrapper.h":
17 | cdef object c_marching_cubes "marching_cubes"(np.ndarray, double) except +
18 | cdef object c_marching_cubes2 "marching_cubes2"(np.ndarray, double) except +
19 | cdef object c_marching_cubes3 "marching_cubes3"(np.ndarray, double) except +
20 | cdef object c_marching_cubes_func "marching_cubes_func"(tuple, tuple, int, int, int, object, double) except +
21 |
22 | def marching_cubes(np.ndarray volume, float isovalue):
23 |
24 | verts, faces = c_marching_cubes(volume, isovalue)
25 | verts.shape = (-1, 3)
26 | faces.shape = (-1, 3)
27 | return verts, faces
28 |
29 | def marching_cubes2(np.ndarray volume, float isovalue):
30 |
31 | verts, faces = c_marching_cubes2(volume, isovalue)
32 | verts.shape = (-1, 3)
33 | faces.shape = (-1, 3)
34 | return verts, faces
35 |
36 | def marching_cubes3(np.ndarray volume, float isovalue):
37 |
38 | verts, faces = c_marching_cubes3(volume, isovalue)
39 | verts.shape = (-1, 3)
40 | faces.shape = (-1, 3)
41 | return verts, faces
42 |
43 | def marching_cubes_func(tuple lower, tuple upper, int numx, int numy, int numz, object f, double isovalue):
44 |
45 | verts, faces = c_marching_cubes_func(lower, upper, numx, numy, numz, f, isovalue)
46 | verts.shape = (-1, 3)
47 | faces.shape = (-1, 3)
48 | return verts, faces
49 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libmcubes/pyarray_symbol.h:
--------------------------------------------------------------------------------
1 |
2 | #define PY_ARRAY_UNIQUE_SYMBOL mcubes_PyArray_API
3 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libmcubes/pyarraymodule.h:
--------------------------------------------------------------------------------
1 |
2 | #ifndef _EXTMODULE_H
3 | #define _EXTMODULE_H
4 |
5 | #include
6 | #include
7 |
8 | // #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
9 | #define PY_ARRAY_UNIQUE_SYMBOL mcubes_PyArray_API
10 | #define NO_IMPORT_ARRAY
11 | #include "numpy/arrayobject.h"
12 |
13 | #include
14 |
15 | template
16 | struct numpy_typemap;
17 |
18 | #define define_numpy_type(ctype, dtype) \
19 | template<> \
20 | struct numpy_typemap \
21 | {static const int type = dtype;};
22 |
23 | define_numpy_type(bool, NPY_BOOL);
24 | define_numpy_type(char, NPY_BYTE);
25 | define_numpy_type(short, NPY_SHORT);
26 | define_numpy_type(int, NPY_INT);
27 | define_numpy_type(long, NPY_LONG);
28 | define_numpy_type(long long, NPY_LONGLONG);
29 | define_numpy_type(unsigned char, NPY_UBYTE);
30 | define_numpy_type(unsigned short, NPY_USHORT);
31 | define_numpy_type(unsigned int, NPY_UINT);
32 | define_numpy_type(unsigned long, NPY_ULONG);
33 | define_numpy_type(unsigned long long, NPY_ULONGLONG);
34 | define_numpy_type(float, NPY_FLOAT);
35 | define_numpy_type(double, NPY_DOUBLE);
36 | define_numpy_type(long double, NPY_LONGDOUBLE);
37 | define_numpy_type(std::complex, NPY_CFLOAT);
38 | define_numpy_type(std::complex, NPY_CDOUBLE);
39 | define_numpy_type(std::complex, NPY_CLONGDOUBLE);
40 |
41 | template
42 | T PyArray_SafeGet(const PyArrayObject* aobj, const npy_intp* indaux)
43 | {
44 | // HORROR.
45 | npy_intp* ind = const_cast(indaux);
46 | void* ptr = PyArray_GetPtr(const_cast(aobj), ind);
47 | switch(PyArray_TYPE(aobj))
48 | {
49 | case NPY_BOOL:
50 | return static_cast(*reinterpret_cast(ptr));
51 | case NPY_BYTE:
52 | return static_cast(*reinterpret_cast(ptr));
53 | case NPY_SHORT:
54 | return static_cast(*reinterpret_cast(ptr));
55 | case NPY_INT:
56 | return static_cast(*reinterpret_cast(ptr));
57 | case NPY_LONG:
58 | return static_cast(*reinterpret_cast(ptr));
59 | case NPY_LONGLONG:
60 | return static_cast(*reinterpret_cast(ptr));
61 | case NPY_UBYTE:
62 | return static_cast(*reinterpret_cast(ptr));
63 | case NPY_USHORT:
64 | return static_cast(*reinterpret_cast(ptr));
65 | case NPY_UINT:
66 | return static_cast(*reinterpret_cast(ptr));
67 | case NPY_ULONG:
68 | return static_cast(*reinterpret_cast(ptr));
69 | case NPY_ULONGLONG:
70 | return static_cast(*reinterpret_cast(ptr));
71 | case NPY_FLOAT:
72 | return static_cast(*reinterpret_cast(ptr));
73 | case NPY_DOUBLE:
74 | return static_cast(*reinterpret_cast(ptr));
75 | case NPY_LONGDOUBLE:
76 | return static_cast(*reinterpret_cast(ptr));
77 | default:
78 | throw std::runtime_error("data type not supported");
79 | }
80 | }
81 |
82 | template
83 | T PyArray_SafeSet(PyArrayObject* aobj, const npy_intp* indaux, const T& value)
84 | {
85 | // HORROR.
86 | npy_intp* ind = const_cast(indaux);
87 | void* ptr = PyArray_GetPtr(aobj, ind);
88 | switch(PyArray_TYPE(aobj))
89 | {
90 | case NPY_BOOL:
91 | *reinterpret_cast(ptr) = static_cast(value);
92 | break;
93 | case NPY_BYTE:
94 | *reinterpret_cast(ptr) = static_cast(value);
95 | break;
96 | case NPY_SHORT:
97 | *reinterpret_cast(ptr) = static_cast(value);
98 | break;
99 | case NPY_INT:
100 | *reinterpret_cast(ptr) = static_cast(value);
101 | break;
102 | case NPY_LONG:
103 | *reinterpret_cast(ptr) = static_cast(value);
104 | break;
105 | case NPY_LONGLONG:
106 | *reinterpret_cast(ptr) = static_cast(value);
107 | break;
108 | case NPY_UBYTE:
109 | *reinterpret_cast(ptr) = static_cast(value);
110 | break;
111 | case NPY_USHORT:
112 | *reinterpret_cast(ptr) = static_cast(value);
113 | break;
114 | case NPY_UINT:
115 | *reinterpret_cast(ptr) = static_cast(value);
116 | break;
117 | case NPY_ULONG:
118 | *reinterpret_cast(ptr) = static_cast(value);
119 | break;
120 | case NPY_ULONGLONG:
121 | *reinterpret_cast(ptr) = static_cast(value);
122 | break;
123 | case NPY_FLOAT:
124 | *reinterpret_cast(ptr) = static_cast(value);
125 | break;
126 | case NPY_DOUBLE:
127 | *reinterpret_cast(ptr) = static_cast(value);
128 | break;
129 | case NPY_LONGDOUBLE:
130 | *reinterpret_cast(ptr) = static_cast(value);
131 | break;
132 | default:
133 | throw std::runtime_error("data type not supported");
134 | }
135 | }
136 |
137 | #endif
138 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libmcubes/pywrapper.cpp:
--------------------------------------------------------------------------------
1 |
2 | #include "pywrapper.h"
3 |
4 | #include "marchingcubes.h"
5 |
6 | #include
7 |
8 | struct PythonToCFunc
9 | {
10 | PyObject* func;
11 | PythonToCFunc(PyObject* func) {this->func = func;}
12 | double operator()(double x, double y, double z)
13 | {
14 | PyObject* res = PyObject_CallFunction(func, "(d,d,d)", x, y, z); // py::extract(func(x,y,z));
15 | if(res == NULL)
16 | return 0.0;
17 |
18 | double result = PyFloat_AsDouble(res);
19 | Py_DECREF(res);
20 | return result;
21 | }
22 | };
23 |
24 | PyObject* marching_cubes_func(PyObject* lower, PyObject* upper,
25 | int numx, int numy, int numz, PyObject* f, double isovalue)
26 | {
27 | std::vector vertices;
28 | std::vector polygons;
29 |
30 | // Copy the lower and upper coordinates to a C array.
31 | double lower_[3];
32 | double upper_[3];
33 | for(int i=0; i<3; ++i)
34 | {
35 | PyObject* l = PySequence_GetItem(lower, i);
36 | if(l == NULL)
37 | throw std::runtime_error("error");
38 | PyObject* u = PySequence_GetItem(upper, i);
39 | if(u == NULL)
40 | {
41 | Py_DECREF(l);
42 | throw std::runtime_error("error");
43 | }
44 |
45 | lower_[i] = PyFloat_AsDouble(l);
46 | upper_[i] = PyFloat_AsDouble(u);
47 |
48 | Py_DECREF(l);
49 | Py_DECREF(u);
50 | if(lower_[i]==-1.0 || upper_[i]==-1.0)
51 | {
52 | if(PyErr_Occurred())
53 | throw std::runtime_error("error");
54 | }
55 | }
56 |
57 | // Marching cubes.
58 | mc::marching_cubes(lower_, upper_, numx, numy, numz, PythonToCFunc(f), isovalue, vertices, polygons);
59 |
60 | // Copy the result to two Python ndarrays.
61 | npy_intp size_vertices = vertices.size();
62 | npy_intp size_polygons = polygons.size();
63 | PyArrayObject* verticesarr = reinterpret_cast(PyArray_SimpleNew(1, &size_vertices, PyArray_DOUBLE));
64 | PyArrayObject* polygonsarr = reinterpret_cast(PyArray_SimpleNew(1, &size_polygons, PyArray_ULONG));
65 |
66 | std::vector::const_iterator it = vertices.begin();
67 | for(int i=0; it!=vertices.end(); ++i, ++it)
68 | *reinterpret_cast(PyArray_GETPTR1(verticesarr, i)) = *it;
69 | std::vector::const_iterator it2 = polygons.begin();
70 | for(int i=0; it2!=polygons.end(); ++i, ++it2)
71 | *reinterpret_cast(PyArray_GETPTR1(polygonsarr, i)) = *it2;
72 |
73 | PyObject* res = Py_BuildValue("(O,O)", verticesarr, polygonsarr);
74 | Py_XDECREF(verticesarr);
75 | Py_XDECREF(polygonsarr);
76 | return res;
77 | }
78 |
79 | struct PyArrayToCFunc
80 | {
81 | PyArrayObject* arr;
82 | PyArrayToCFunc(PyArrayObject* arr) {this->arr = arr;}
83 | double operator()(int x, int y, int z)
84 | {
85 | npy_intp c[3] = {x,y,z};
86 | return PyArray_SafeGet(arr, c);
87 | }
88 | };
89 |
90 | PyObject* marching_cubes(PyArrayObject* arr, double isovalue)
91 | {
92 | if(PyArray_NDIM(arr) != 3)
93 | throw std::runtime_error("Only three-dimensional arrays are supported.");
94 |
95 | // Prepare data.
96 | npy_intp* shape = PyArray_DIMS(arr);
97 | double lower[3] = {0,0,0};
98 | double upper[3] = {shape[0]-1, shape[1]-1, shape[2]-1};
99 | long numx = upper[0] - lower[0] + 1;
100 | long numy = upper[1] - lower[1] + 1;
101 | long numz = upper[2] - lower[2] + 1;
102 | std::vector vertices;
103 | std::vector polygons;
104 |
105 | // Marching cubes.
106 | mc::marching_cubes(lower, upper, numx, numy, numz, PyArrayToCFunc(arr), isovalue,
107 | vertices, polygons);
108 |
109 | // Copy the result to two Python ndarrays.
110 | npy_intp size_vertices = vertices.size();
111 | npy_intp size_polygons = polygons.size();
112 | PyArrayObject* verticesarr = reinterpret_cast(PyArray_SimpleNew(1, &size_vertices, PyArray_DOUBLE));
113 | PyArrayObject* polygonsarr = reinterpret_cast(PyArray_SimpleNew(1, &size_polygons, PyArray_ULONG));
114 |
115 | std::vector::const_iterator it = vertices.begin();
116 | for(int i=0; it!=vertices.end(); ++i, ++it)
117 | *reinterpret_cast(PyArray_GETPTR1(verticesarr, i)) = *it;
118 | std::vector::const_iterator it2 = polygons.begin();
119 | for(int i=0; it2!=polygons.end(); ++i, ++it2)
120 | *reinterpret_cast(PyArray_GETPTR1(polygonsarr, i)) = *it2;
121 |
122 | PyObject* res = Py_BuildValue("(O,O)", verticesarr, polygonsarr);
123 | Py_XDECREF(verticesarr);
124 | Py_XDECREF(polygonsarr);
125 |
126 | return res;
127 | }
128 |
129 | PyObject* marching_cubes2(PyArrayObject* arr, double isovalue)
130 | {
131 | if(PyArray_NDIM(arr) != 3)
132 | throw std::runtime_error("Only three-dimensional arrays are supported.");
133 |
134 | // Prepare data.
135 | npy_intp* shape = PyArray_DIMS(arr);
136 | double lower[3] = {0,0,0};
137 | double upper[3] = {shape[0]-1, shape[1]-1, shape[2]-1};
138 | long numx = upper[0] - lower[0] + 1;
139 | long numy = upper[1] - lower[1] + 1;
140 | long numz = upper[2] - lower[2] + 1;
141 | std::vector vertices;
142 | std::vector polygons;
143 |
144 | // Marching cubes.
145 | mc::marching_cubes2(lower, upper, numx, numy, numz, PyArrayToCFunc(arr), isovalue,
146 | vertices, polygons);
147 |
148 | // Copy the result to two Python ndarrays.
149 | npy_intp size_vertices = vertices.size();
150 | npy_intp size_polygons = polygons.size();
151 | PyArrayObject* verticesarr = reinterpret_cast(PyArray_SimpleNew(1, &size_vertices, PyArray_DOUBLE));
152 | PyArrayObject* polygonsarr = reinterpret_cast(PyArray_SimpleNew(1, &size_polygons, PyArray_ULONG));
153 |
154 | std::vector::const_iterator it = vertices.begin();
155 | for(int i=0; it!=vertices.end(); ++i, ++it)
156 | *reinterpret_cast(PyArray_GETPTR1(verticesarr, i)) = *it;
157 | std::vector::const_iterator it2 = polygons.begin();
158 | for(int i=0; it2!=polygons.end(); ++i, ++it2)
159 | *reinterpret_cast(PyArray_GETPTR1(polygonsarr, i)) = *it2;
160 |
161 | PyObject* res = Py_BuildValue("(O,O)", verticesarr, polygonsarr);
162 | Py_XDECREF(verticesarr);
163 | Py_XDECREF(polygonsarr);
164 |
165 | return res;
166 | }
167 |
168 | PyObject* marching_cubes3(PyArrayObject* arr, double isovalue)
169 | {
170 | if(PyArray_NDIM(arr) != 3)
171 | throw std::runtime_error("Only three-dimensional arrays are supported.");
172 |
173 | // Prepare data.
174 | npy_intp* shape = PyArray_DIMS(arr);
175 | double lower[3] = {0,0,0};
176 | double upper[3] = {shape[0]-1, shape[1]-1, shape[2]-1};
177 | long numx = upper[0] - lower[0] + 1;
178 | long numy = upper[1] - lower[1] + 1;
179 | long numz = upper[2] - lower[2] + 1;
180 | std::vector vertices;
181 | std::vector polygons;
182 |
183 | // Marching cubes.
184 | mc::marching_cubes3(lower, upper, numx, numy, numz, PyArrayToCFunc(arr), isovalue,
185 | vertices, polygons);
186 |
187 | // Copy the result to two Python ndarrays.
188 | npy_intp size_vertices = vertices.size();
189 | npy_intp size_polygons = polygons.size();
190 | PyArrayObject* verticesarr = reinterpret_cast(PyArray_SimpleNew(1, &size_vertices, PyArray_DOUBLE));
191 | PyArrayObject* polygonsarr = reinterpret_cast(PyArray_SimpleNew(1, &size_polygons, PyArray_ULONG));
192 |
193 | std::vector::const_iterator it = vertices.begin();
194 | for(int i=0; it!=vertices.end(); ++i, ++it)
195 | *reinterpret_cast(PyArray_GETPTR1(verticesarr, i)) = *it;
196 | std::vector::const_iterator it2 = polygons.begin();
197 | for(int i=0; it2!=polygons.end(); ++i, ++it2)
198 | *reinterpret_cast(PyArray_GETPTR1(polygonsarr, i)) = *it2;
199 |
200 | PyObject* res = Py_BuildValue("(O,O)", verticesarr, polygonsarr);
201 | Py_XDECREF(verticesarr);
202 | Py_XDECREF(polygonsarr);
203 |
204 | return res;
205 | }
--------------------------------------------------------------------------------
/watertight_transformer/external/libmcubes/pywrapper.h:
--------------------------------------------------------------------------------
1 |
2 | #ifndef _PYWRAPPER_H
3 | #define _PYWRAPPER_H
4 |
5 | #include
6 | #include "pyarraymodule.h"
7 |
8 | #include
9 |
10 | PyObject* marching_cubes(PyArrayObject* arr, double isovalue);
11 | PyObject* marching_cubes2(PyArrayObject* arr, double isovalue);
12 | PyObject* marching_cubes3(PyArrayObject* arr, double isovalue);
13 | PyObject* marching_cubes_func(PyObject* lower, PyObject* upper,
14 | int numx, int numy, int numz, PyObject* f, double isovalue);
15 |
16 | #endif // _PYWRAPPER_H
17 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libmesh/.gitignore:
--------------------------------------------------------------------------------
1 | triangle_hash.cpp
2 | build
3 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libmesh/README.md:
--------------------------------------------------------------------------------
1 | ## License
2 |
3 | License for source code corresponding to:
4 |
5 | Mescheder, Lars and Oechsle, Michael and Niemeyer, Michael and Nowozin, Sebastian and Geiger, Andreas. **Occupancy Networks: Learning 3D Reconstruction in Function Space**, CVPR 2019
6 |
7 | Copyright 2019 Lars Mescheder, Michael Oechsle, Michael Niemeyer, Andreas Geiger, Sebastian Nowozin
8 |
9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
14 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libmesh/__init__.py:
--------------------------------------------------------------------------------
1 | from .inside_mesh import (
2 | check_mesh_contains, MeshIntersector, TriangleIntersector2d
3 | )
4 |
5 |
6 | __all__ = [
7 | check_mesh_contains, MeshIntersector, TriangleIntersector2d
8 | ]
9 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libmesh/inside_mesh.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from .triangle_hash import TriangleHash as _TriangleHash
3 |
4 |
5 | def check_mesh_contains(mesh, points, hash_resolution=512):
6 | intersector = MeshIntersector(mesh, hash_resolution)
7 | contains = intersector.query(points)
8 | return contains
9 |
10 |
11 | class MeshIntersector:
12 | def __init__(self, mesh, resolution=512):
13 | triangles = mesh.vertices[mesh.faces].astype(np.float64)
14 | n_tri = triangles.shape[0]
15 |
16 | self.resolution = resolution
17 | self.bbox_min = triangles.reshape(3 * n_tri, 3).min(axis=0)
18 | self.bbox_max = triangles.reshape(3 * n_tri, 3).max(axis=0)
19 | # Tranlate and scale it to [0.5, self.resolution - 0.5]^3
20 | self.scale = (resolution - 1) / (self.bbox_max - self.bbox_min)
21 | self.translate = 0.5 - self.scale * self.bbox_min
22 |
23 | self._triangles = triangles = self.rescale(triangles)
24 | # assert(np.allclose(triangles.reshape(-1, 3).min(0), 0.5))
25 | # assert(np.allclose(triangles.reshape(-1, 3).max(0), resolution - 0.5))
26 |
27 | triangles2d = triangles[:, :, :2]
28 | self._tri_intersector2d = TriangleIntersector2d(
29 | triangles2d, resolution)
30 |
31 | def query(self, points):
32 | # Rescale points
33 | points = self.rescale(points)
34 |
35 | # placeholder result with no hits we'll fill in later
36 | contains = np.zeros(len(points), dtype=np.bool)
37 |
38 | # cull points outside of the axis aligned bounding box
39 | # this avoids running ray tests unless points are close
40 | inside_aabb = np.all(
41 | (0 <= points) & (points <= self.resolution), axis=1)
42 | if not inside_aabb.any():
43 | return contains
44 |
45 | # Only consider points inside bounding box
46 | mask = inside_aabb
47 | points = points[mask]
48 |
49 | # Compute intersection depth and check order
50 | points_indices, tri_indices = self._tri_intersector2d.query(points[:, :2])
51 |
52 | triangles_intersect = self._triangles[tri_indices]
53 | points_intersect = points[points_indices]
54 |
55 | depth_intersect, abs_n_2 = self.compute_intersection_depth(
56 | points_intersect, triangles_intersect)
57 |
58 | # Count number of intersections in both directions
59 | smaller_depth = depth_intersect >= points_intersect[:, 2] * abs_n_2
60 | bigger_depth = depth_intersect < points_intersect[:, 2] * abs_n_2
61 | points_indices_0 = points_indices[smaller_depth]
62 | points_indices_1 = points_indices[bigger_depth]
63 |
64 | nintersect0 = np.bincount(points_indices_0, minlength=points.shape[0])
65 | nintersect1 = np.bincount(points_indices_1, minlength=points.shape[0])
66 |
67 | # Check if point contained in mesh
68 | contains1 = (np.mod(nintersect0, 2) == 1)
69 | contains2 = (np.mod(nintersect1, 2) == 1)
70 | if (contains1 != contains2).any():
71 | print('Warning: contains1 != contains2 for some points.')
72 | contains[mask] = (contains1 & contains2)
73 | return contains
74 |
75 | def compute_intersection_depth(self, points, triangles):
76 | t1 = triangles[:, 0, :]
77 | t2 = triangles[:, 1, :]
78 | t3 = triangles[:, 2, :]
79 |
80 | v1 = t3 - t1
81 | v2 = t2 - t1
82 | # v1 = v1 / np.linalg.norm(v1, axis=-1, keepdims=True)
83 | # v2 = v2 / np.linalg.norm(v2, axis=-1, keepdims=True)
84 |
85 | normals = np.cross(v1, v2)
86 | alpha = np.sum(normals[:, :2] * (t1[:, :2] - points[:, :2]), axis=1)
87 |
88 | n_2 = normals[:, 2]
89 | t1_2 = t1[:, 2]
90 | s_n_2 = np.sign(n_2)
91 | abs_n_2 = np.abs(n_2)
92 |
93 | mask = (abs_n_2 != 0)
94 |
95 | depth_intersect = np.full(points.shape[0], np.nan)
96 | depth_intersect[mask] = \
97 | t1_2[mask] * abs_n_2[mask] + alpha[mask] * s_n_2[mask]
98 |
99 | # Test the depth:
100 | # TODO: remove and put into tests
101 | # points_new = np.concatenate([points[:, :2], depth_intersect[:, None]], axis=1)
102 | # alpha = (normals * t1).sum(-1)
103 | # mask = (depth_intersect == depth_intersect)
104 | # assert(np.allclose((points_new[mask] * normals[mask]).sum(-1),
105 | # alpha[mask]))
106 | return depth_intersect, abs_n_2
107 |
108 | def rescale(self, array):
109 | array = self.scale * array + self.translate
110 | return array
111 |
112 |
113 | class TriangleIntersector2d:
114 | def __init__(self, triangles, resolution=128):
115 | self.triangles = triangles
116 | self.tri_hash = _TriangleHash(triangles, resolution)
117 |
118 | def query(self, points):
119 | point_indices, tri_indices = self.tri_hash.query(points)
120 | point_indices = np.array(point_indices, dtype=np.int64)
121 | tri_indices = np.array(tri_indices, dtype=np.int64)
122 | points = points[point_indices]
123 | triangles = self.triangles[tri_indices]
124 | mask = self.check_triangles(points, triangles)
125 | point_indices = point_indices[mask]
126 | tri_indices = tri_indices[mask]
127 | return point_indices, tri_indices
128 |
129 | def check_triangles(self, points, triangles):
130 | contains = np.zeros(points.shape[0], dtype=np.bool)
131 | A = triangles[:, :2] - triangles[:, 2:]
132 | A = A.transpose([0, 2, 1])
133 | y = points - triangles[:, 2]
134 |
135 | detA = A[:, 0, 0] * A[:, 1, 1] - A[:, 0, 1] * A[:, 1, 0]
136 |
137 | mask = (np.abs(detA) != 0.)
138 | A = A[mask]
139 | y = y[mask]
140 | detA = detA[mask]
141 |
142 | s_detA = np.sign(detA)
143 | abs_detA = np.abs(detA)
144 |
145 | u = (A[:, 1, 1] * y[:, 0] - A[:, 0, 1] * y[:, 1]) * s_detA
146 | v = (-A[:, 1, 0] * y[:, 0] + A[:, 0, 0] * y[:, 1]) * s_detA
147 |
148 | sum_uv = u + v
149 | contains[mask] = (
150 | (0 < u) & (u < abs_detA) & (0 < v) & (v < abs_detA)
151 | & (0 < sum_uv) & (sum_uv < abs_detA)
152 | )
153 | return contains
154 |
--------------------------------------------------------------------------------
/watertight_transformer/external/libmesh/triangle_hash.pyx:
--------------------------------------------------------------------------------
1 |
2 | # distutils: language=c++
3 | import numpy as np
4 | cimport numpy as np
5 | cimport cython
6 | from libcpp.vector cimport vector
7 | from libc.math cimport floor, ceil
8 |
9 | cdef class TriangleHash:
10 | cdef vector[vector[int]] spatial_hash
11 | cdef int resolution
12 |
13 | def __cinit__(self, double[:, :, :] triangles, int resolution):
14 | self.spatial_hash.resize(resolution * resolution)
15 | self.resolution = resolution
16 | self._build_hash(triangles)
17 |
18 | @cython.boundscheck(False) # Deactivate bounds checking
19 | @cython.wraparound(False) # Deactivate negative indexing.
20 | cdef int _build_hash(self, double[:, :, :] triangles):
21 | assert(triangles.shape[1] == 3)
22 | assert(triangles.shape[2] == 2)
23 |
24 | cdef int n_tri = triangles.shape[0]
25 | cdef int bbox_min[2]
26 | cdef int bbox_max[2]
27 |
28 | cdef int i_tri, j, x, y
29 | cdef int spatial_idx
30 |
31 | for i_tri in range(n_tri):
32 | # Compute bounding box
33 | for j in range(2):
34 | bbox_min[j] = min(
35 | triangles[i_tri, 0, j], triangles[i_tri, 1, j], triangles[i_tri, 2, j]
36 | )
37 | bbox_max[j] = max(
38 | triangles[i_tri, 0, j], triangles[i_tri, 1, j], triangles[i_tri, 2, j]
39 | )
40 | bbox_min[j] = min(max(bbox_min[j], 0), self.resolution - 1)
41 | bbox_max[j] = min(max(bbox_max[j], 0), self.resolution - 1)
42 |
43 | # Find all voxels where bounding box intersects
44 | for x in range(bbox_min[0], bbox_max[0] + 1):
45 | for y in range(bbox_min[1], bbox_max[1] + 1):
46 | spatial_idx = self.resolution * x + y
47 | self.spatial_hash[spatial_idx].push_back(i_tri)
48 |
49 | @cython.boundscheck(False) # Deactivate bounds checking
50 | @cython.wraparound(False) # Deactivate negative indexing.
51 | cpdef query(self, double[:, :] points):
52 | assert(points.shape[1] == 2)
53 | cdef int n_points = points.shape[0]
54 |
55 | cdef vector[int] points_indices
56 | cdef vector[int] tri_indices
57 | # cdef int[:] points_indices_np
58 | # cdef int[:] tri_indices_np
59 |
60 | cdef int i_point, k, x, y
61 | cdef int spatial_idx
62 |
63 | for i_point in range(n_points):
64 | x = int(points[i_point, 0])
65 | y = int(points[i_point, 1])
66 | if not (0 <= x < self.resolution and 0 <= y < self.resolution):
67 | continue
68 |
69 | spatial_idx = self.resolution * x + y
70 | for i_tri in self.spatial_hash[spatial_idx]:
71 | points_indices.push_back(i_point)
72 | tri_indices.push_back(i_tri)
73 |
74 | points_indices_np = np.zeros(points_indices.size(), dtype=np.int32)
75 | tri_indices_np = np.zeros(tri_indices.size(), dtype=np.int32)
76 |
77 | cdef int[:] points_indices_view = points_indices_np
78 | cdef int[:] tri_indices_view = tri_indices_np
79 |
80 | for k in range(points_indices.size()):
81 | points_indices_view[k] = points_indices[k]
82 |
83 | for k in range(tri_indices.size()):
84 | tri_indices_view[k] = tri_indices[k]
85 |
86 | return points_indices_np, tri_indices_np
87 |
88 |
--------------------------------------------------------------------------------
/watertight_transformer/external/librender/README.md:
--------------------------------------------------------------------------------
1 | ## License
2 |
3 | License for source code corresponding to:
4 |
5 | Stutz, David and Geiger, Andreas. **Learning 3D Shape Completion under Weak Supervision**, CVPR 2018
6 |
7 | Copyright 2018 David Stutz and Andreas Geiger
8 |
9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
14 |
15 |
--------------------------------------------------------------------------------
/watertight_transformer/external/librender/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paschalidoud/mesh_fusion_simple/aaefe7a0acf8f8f6f5ac611e0636f863e0154fd2/watertight_transformer/external/librender/__init__.py
--------------------------------------------------------------------------------
/watertight_transformer/external/librender/offscreen.cpp:
--------------------------------------------------------------------------------
1 | #include "offscreen.h"
2 | #include
3 |
4 | int OffscreenGL::glutWin = -1;
5 | bool OffscreenGL::glutInitialized = false;
6 |
7 | OffscreenGL::OffscreenGL(int maxHeight, int maxWidth) {
8 |
9 | if (!glutInitialized) {
10 | int argc = 1;
11 | char *argv = "test";
12 | glutInit(&argc, &argv);
13 | glutInitialized = true;
14 | }
15 |
16 | glutInitDisplayMode(GLUT_DEPTH | GLUT_SINGLE | GLUT_RGBA);
17 | glutInitWindowPosition(100, 100);
18 | glutInitWindowSize(maxWidth, maxHeight);
19 |
20 | // create or set window & off-screen framebuffer
21 | if (glutWin < 0) {
22 |
23 | glutWin = glutCreateWindow("OpenGL");
24 | glutHideWindow();
25 | glewInit();
26 | glGenFramebuffersEXT(1, &fb);
27 |
28 | glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
29 | glGenTextures(1, &renderTex);
30 | glActiveTexture(GL_TEXTURE0);
31 | glBindTexture(GL_TEXTURE_RECTANGLE_ARB, renderTex);
32 | glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
33 | glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
34 | glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
35 | glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
36 | glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
37 | glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGB, maxWidth, maxHeight,
38 | 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
39 |
40 | glGenTextures(1, &depthTex);
41 | glBindTexture(GL_TEXTURE_RECTANGLE_ARB, depthTex);
42 | glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
43 | glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
44 | glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
45 | glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
46 | glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);
47 | glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
48 | glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
49 | glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_DEPTH24_STENCIL8, maxWidth, maxHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL);
50 |
51 | glGenFramebuffersEXT(1, &fb);
52 | glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
53 | glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_ARB, renderTex, 0);
54 | glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_RECTANGLE_ARB, depthTex, 0);
55 | glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT|GL_DEPTH_ATTACHMENT_EXT);
56 | } else {
57 | glutSetWindow(glutWin);
58 | }
59 | }
60 |
61 | OffscreenGL::~OffscreenGL() {
62 | }
63 |
64 |
65 | GLuint createDisplayList(double *fM, int fNum, double *vM, int vNum, double *cM, unsigned int colorModFactor, double linewidth, bool coloring) {
66 |
67 | GLuint theShape;
68 | int i;
69 | unsigned int channelCapacity, channelCapacity2;
70 | double *fp;
71 | int vIndex, fNum2;
72 | fNum2 = fNum*2;
73 |
74 | channelCapacity = 256 / colorModFactor;
75 | channelCapacity2 = channelCapacity * channelCapacity;
76 |
77 | theShape = glGenLists(1);
78 |
79 | glNewList(theShape, GL_COMPILE);
80 |
81 | if (linewidth>0.1) {
82 | glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
83 | glLineWidth(linewidth);
84 | }
85 |
86 | glBegin(GL_TRIANGLES);
87 | for (i = 1; i <= fNum; i++) {
88 | fp = fM + i-1;
89 |
90 | vIndex = (int)fp[0] - 1;
91 | if (coloring) glColor3ub(cM[vIndex], cM[vIndex + vNum], cM[vIndex + 2*vNum]);
92 | glVertex3d(vM[vIndex], vM[vIndex + vNum], vM[vIndex + 2*vNum]);
93 |
94 | vIndex = (int)fp[fNum] - 1;
95 | if (coloring) glColor3ub(cM[vIndex], cM[vIndex + vNum], cM[vIndex + 2*vNum]);
96 | glVertex3d(vM[vIndex], vM[vIndex + vNum], vM[vIndex + 2*vNum]);
97 |
98 | vIndex = (int)fp[fNum2] - 1;
99 | if (coloring) glColor3ub(cM[vIndex], cM[vIndex + vNum], cM[vIndex + 2*vNum]);
100 | glVertex3d(vM[vIndex], vM[vIndex + vNum], vM[vIndex + 2*vNum]);
101 | }
102 | glEnd();
103 | if (linewidth>0.1)
104 | glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
105 | glEndList();
106 |
107 | return theShape;
108 | }
109 |
110 | void cameraSetup(double zNear, double zFar, double *intrinsics, unsigned int imgHeight, unsigned int imgWidth) {
111 |
112 | double viewMat[] = {1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1};
113 | double fcv[] = {intrinsics[0], intrinsics[1]};
114 | double ccv[] = {intrinsics[2], intrinsics[3]};
115 |
116 | glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
117 | glEnable(GL_DEPTH_TEST);
118 | glDisable(GL_TEXTURE_2D);
119 |
120 | glMatrixMode(GL_MODELVIEW);
121 | glLoadMatrixd(viewMat);
122 |
123 | double left = - ccv[0] / fcv[0] * zNear;
124 | double bottom = (ccv[1] - (double)(imgHeight-1)) / fcv[1] * zNear;
125 | double right = ((double)imgWidth - 1.0 - ccv[0]) / fcv[0] * zNear;
126 | double top = ccv[1] / fcv[1] * zNear;
127 |
128 | glMatrixMode(GL_PROJECTION);
129 | glLoadIdentity();
130 | glFrustum(left, right, bottom, top, zNear, zFar);
131 | glViewport(0, 0, imgWidth, imgHeight);
132 | }
133 |
134 | void drawPatchToDepthBuffer(GLuint listName, unsigned char *imageBuffer, float *depthBuffer, bool *maskBuffer,
135 | unsigned int imgHeight, unsigned int imgWidth, double *zNearFarV, bool coloring = true) {
136 |
137 | glCallList(listName);
138 | glFlush();
139 |
140 | // bug fix for Nvidia
141 | unsigned int paddedWidth = imgWidth % 4;
142 | if (paddedWidth != 0) paddedWidth = 4 - paddedWidth + imgWidth;
143 | else paddedWidth = imgWidth;
144 |
145 | // Read off of the depth buffer
146 | float *dataBuffer_depth = (float *)malloc(paddedWidth * imgHeight * sizeof(GL_FLOAT));
147 | glReadPixels(0, 0, paddedWidth, imgHeight, GL_DEPTH_COMPONENT, GL_FLOAT, dataBuffer_depth);
148 |
149 | // Read off of the color buffer
150 | GLubyte *dataBuffer_rgb = (GLubyte *)malloc(3* paddedWidth * imgHeight * sizeof(GLubyte));
151 | if (coloring)
152 | glReadPixels(0, 0, paddedWidth, imgHeight, GL_RGB, GL_UNSIGNED_BYTE, dataBuffer_rgb);
153 |
154 | // reorder the pixel data for the opengl to matlab conversion
155 | unsigned int matlabImgIndex = 0;
156 | unsigned int oglImageIndex = 0;
157 |
158 | float n = zNearFarV[0];
159 | float f = zNearFarV[1];
160 | for (int j = 0; j < imgWidth; j++) {
161 | for (int i = 0; i < imgHeight; i++, matlabImgIndex++) {
162 | oglImageIndex = (j + (imgHeight-1-i) * paddedWidth);
163 | float depth = dataBuffer_depth[oglImageIndex];
164 |
165 | // render mask: indicating points inside the clipped plane
166 | maskBuffer[matlabImgIndex] = depth<1;
167 |
168 | // render depth
169 | depthBuffer[matlabImgIndex] = -f*n/(depth*(f-n)-f);
170 |
171 | // render color
172 | if (coloring) {
173 | imageBuffer[matlabImgIndex] = (unsigned char) dataBuffer_rgb[oglImageIndex*3];
174 | imageBuffer[matlabImgIndex+imgWidth*imgHeight] = (unsigned char) dataBuffer_rgb[oglImageIndex*3+1];
175 | imageBuffer[matlabImgIndex+imgWidth*imgHeight*2] = (unsigned char) dataBuffer_rgb[oglImageIndex*3+2];
176 | }
177 | }
178 | }
179 |
180 | free(dataBuffer_depth);
181 | free(dataBuffer_rgb);
182 | }
183 |
184 | void renderDepthMesh(double *FM, int fNum, double *VM, int vNum, double *CM, double *intrinsics, int *imgSizeV, double *zNearFarV, unsigned char * imgBuffer, float *depthBuffer, bool *maskBuffer, double linewidth, bool coloring) {
185 | //createGLContext();
186 | OffscreenGL offscreenGL(imgSizeV[0], imgSizeV[1]);
187 | cameraSetup(zNearFarV[0], zNearFarV[1], intrinsics, imgSizeV[0], imgSizeV[1]);
188 | GLuint list = createDisplayList(FM, fNum, VM, vNum, CM, 1, linewidth, coloring);
189 | drawPatchToDepthBuffer(list, imgBuffer, depthBuffer, maskBuffer, imgSizeV[0], imgSizeV[1], zNearFarV, coloring);
190 | if (list) {
191 | glDeleteLists(list, 1);
192 | list = 0;
193 | }
194 | //deleteGLContext();
195 | }
196 |
--------------------------------------------------------------------------------
/watertight_transformer/external/librender/offscreen.h:
--------------------------------------------------------------------------------
1 | #ifndef LIBRENDER_OFFSCREEN_H
2 | #define LIBRENDER_OFFSCREEN_H
3 |
4 | #include "GL/glew.h"
5 | #include "GL/gl.h"
6 | #include "GL/glu.h"
7 | #include "GL/glut.h"
8 |
9 | class OffscreenGL {
10 |
11 | public:
12 | OffscreenGL(int maxHeight, int maxWidth);
13 | ~OffscreenGL();
14 |
15 | private:
16 | static int glutWin;
17 | static bool glutInitialized;
18 | GLuint fb;
19 | GLuint renderTex;
20 | GLuint depthTex;
21 | };
22 |
23 |
24 | void renderDepthMesh(double *FM, int fNum, double *VM, int vNum, double *CM, double *intrinsics, int *imgSizeV, double *zNearFarV, unsigned char * imgBuffer, float *depthBuffer, bool *maskBuffer, double linewidth, bool coloring);
25 |
26 | #endif
--------------------------------------------------------------------------------
/watertight_transformer/external/librender/pyrender.pyx:
--------------------------------------------------------------------------------
1 | cimport cython
2 | import numpy as np
3 | cimport numpy as np
4 |
5 | from libc.stdlib cimport free, malloc
6 | from libcpp cimport bool
7 | from cpython cimport PyObject, Py_INCREF
8 |
9 | CREATE_INIT = True # workaround, so cython builds a init function
10 |
11 | np.import_array()
12 |
13 |
14 | cdef extern from "offscreen.h":
15 | void renderDepthMesh(double *FM, int fNum, double *VM, int vNum, double *CM, double *intrinsics, int *imgSizeV, double *zNearFarV, unsigned char * imgBuffer, float *depthBuffer, bool *maskBuffer, double linewidth, bool coloring);
16 |
17 |
18 | def render(double[:,::1] vertices, double[:,::1] faces, double[::1] cam_intr, double[::1] znf, int[::1] img_size):
19 | if vertices.shape[0] != 3:
20 | raise Exception('vertices must be a 3xM double array')
21 | if faces.shape[0] != 3:
22 | raise Exception('faces must be a 3xM double array')
23 | if cam_intr.shape[0] != 4:
24 | raise Exception('cam_intr must be a 4x1 double vector')
25 | if img_size.shape[0] != 2:
26 | raise Exception('img_size must be a 2x1 int vector')
27 |
28 | cdef double* VM = &(vertices[0,0])
29 | cdef int vNum = vertices.shape[1]
30 | cdef double* FM = &(faces[0,0])
31 | cdef int fNum = faces.shape[1]
32 | cdef double* intrinsics = &(cam_intr[0])
33 | cdef double* zNearVarV = &(znf[0])
34 | cdef int* imgSize = &(img_size[0])
35 |
36 | cdef bool coloring = False
37 | cdef double* CM = NULL
38 |
39 | depth = np.empty((img_size[1], img_size[0]), dtype=np.float32)
40 | mask = np.empty((img_size[1], img_size[0]), dtype=np.uint8)
41 | img = np.empty((3, img_size[1], img_size[0]), dtype=np.uint8)
42 | cdef float[:,::1] depth_view = depth
43 | cdef unsigned char[:,::1] mask_view = mask
44 | cdef unsigned char[:,:,::1] img_view = img
45 | cdef float* depthBuffer = &(depth_view[0,0])
46 | cdef bool* maskBuffer = &(mask_view[0,0])
47 | cdef unsigned char* imgBuffer = &(img_view[0,0,0])
48 |
49 | renderDepthMesh(FM, fNum, VM, vNum, CM, intrinsics, imgSize, zNearVarV, imgBuffer, depthBuffer, maskBuffer, 0, coloring);
50 |
51 | return depth.T, mask.T, img.transpose((2,1,0))
52 |
--------------------------------------------------------------------------------
/watertight_transformer/manifoldplus.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 |
3 | import trimesh
4 |
5 | from simple_3dviz import Mesh
6 |
7 |
8 | class ManifoldPlus:
9 | """Performs the watertight conversion using the Manifold algorithm from [1]
10 |
11 | [1] ManifoldPlus: A Robust and Scalable Watertight Manifold Surface
12 | Generation Method for Triangle Soups, by Huang, Jingwei and Zhou, Yichao
13 | and Guibas, Leonidas
14 | """
15 | def __init__(
16 | self,
17 | manifoldplus_script,
18 | depth=10,
19 | ):
20 | self.manifoldplus_script = manifoldplus_script
21 | self.depth = depth
22 |
23 | def to_watertight(self, path_to_mesh, path_to_watertight, file_type="off"):
24 | subprocess.call([
25 | self.manifoldplus_script,
26 | "--input", path_to_mesh,
27 | "--output", path_to_watertight,
28 | "--depth", str(self.depth),
29 | ], stdout=subprocess.DEVNULL)
30 |
31 |
--------------------------------------------------------------------------------
/watertight_transformer/tsdf_fusion.py:
--------------------------------------------------------------------------------
1 | import math
2 |
3 | import numpy as np
4 | import trimesh
5 | from scipy import ndimage
6 |
7 | from .external.libfusioncpu import cyfusion as libfusion
8 | from .external.libfusioncpu.cyfusion import tsdf_cpu as compute_tsdf
9 | from .external.libmcubes import mcubes
10 | from .external.librender import pyrender
11 | from .utils import read_hdf5, write_hdf5
12 |
13 |
14 | class TSDFFusion:
15 | """Perform the TSDF fusion.
16 | Code adapted from
17 | https://github.com/davidstutz/mesh-fusion/blob/master/2_fusion.py
18 | """
19 | def __init__(
20 | self,
21 | image_height=640,
22 | image_width=640,
23 | focal_length_x=640,
24 | focal_length_y=640,
25 | principal_point_x=320,
26 | principal_point_y=320,
27 | resolution=256,
28 | truncation_factor=15,
29 | n_views=100,
30 | depth_offset_factor=1.5
31 | ):
32 | self.fx = focal_length_x
33 | self.fy = focal_length_y
34 | self.ppx = principal_point_x
35 | self.ppy = principal_point_y
36 | self.image_height = image_height
37 | self.image_width = image_width
38 | self.resolution = resolution
39 | self.truncation_factor = truncation_factor
40 | self.n_views = n_views
41 | self.depth_offset_factor = depth_offset_factor
42 |
43 | self.render_intrinsics = np.array([
44 | self.fx, self.fy, self.ppx, self.ppy
45 | ], dtype=float)
46 | # Essentially the same as above, just a slightly different format.
47 | self.fusion_intrisics = np.array([
48 | [self.fx, 0, self.ppx],
49 | [0, self.fy, self.ppy],
50 | [0, 0, 1]
51 | ])
52 | self.image_size = np.array([
53 | self.image_height, self.image_width,
54 | ], dtype=np.int32)
55 | # Mesh will be centered at (0, 0, 1)!
56 | self.znf = np.array([
57 | 1 - 0.75,
58 | 1 + 0.75
59 | ], dtype=float)
60 | # Derive voxel size from resolution.
61 | self.voxel_size = 1.0 / self.resolution
62 | self.truncation = self.truncation_factor * self.voxel_size
63 |
64 | def get_points_on_sphere(self):
65 | """Code adapted from
66 |
67 | https://stackoverflow.com/questions/9600801/evenly-distributing-n-points-on-a-sphere.
68 | """
69 | rnd = 1.
70 | points = []
71 | offset = 2. / self.n_views
72 | increment = math.pi * (3. - math.sqrt(5.));
73 |
74 | for i in range(self.n_views):
75 | y = ((i * offset) - 1) + (offset / 2);
76 | r = math.sqrt(1 - pow(y, 2))
77 |
78 | phi = ((i + rnd) % self.n_views) * increment
79 |
80 | x = math.cos(phi) * r
81 | z = math.sin(phi) * r
82 |
83 | points.append([x, y, z])
84 |
85 | return np.array(points)
86 |
87 | def get_views(self):
88 | """Generate a set of views to generate depth maps from.
89 | """
90 | Rs = []
91 | points = self.get_points_on_sphere()
92 |
93 | for i in range(points.shape[0]):
94 | # Code adapted from
95 | # https://math.stackexchange.com/questions/1465611/given-a-point-on-a-sphere-how-do-i-find-the-angles-needed-to-point-at-its-ce
96 | longitude = - math.atan2(points[i, 0], points[i, 1])
97 | latitude = math.atan2(
98 | points[i, 2],
99 | math.sqrt(points[i, 0] ** 2 + points[i, 1] ** 2)
100 | )
101 |
102 | R_x = np.array([
103 | [1, 0, 0],
104 | [0, math.cos(latitude), -math.sin(latitude)],
105 | [0, math.sin(latitude), math.cos(latitude)]
106 | ])
107 | R_y = np.array([
108 | [math.cos(longitude), 0, math.sin(longitude)],
109 | [0, 1, 0],
110 | [-math.sin(longitude), 0, math.cos(longitude)]
111 | ])
112 | R = R_y.dot(R_x)
113 | Rs.append(R)
114 |
115 | return Rs
116 |
117 | def render(self, mesh, Rs, output_path=None):
118 | """Render the given mesh using the generated views.
119 |
120 | Arguments:
121 | -----------
122 | mesh: trimesh.Mesh object
123 | Rs: rotation matrices
124 | output_path: path to store the computed depth maps
125 | """
126 | depthmaps = []
127 | for i in range(len(Rs)):
128 | np_vertices = Rs[i].dot(mesh.vertices.astype(np.float64).T)
129 | np_vertices[2, :] += 1
130 |
131 | np_faces = mesh.faces.astype(np.float64)
132 | np_faces += 1
133 |
134 | depthmap, mask, img = pyrender.render(
135 | np_vertices.copy(),
136 | np_faces.T.copy(),
137 | self.render_intrinsics,
138 | self.znf,
139 | self.image_size
140 | )
141 |
142 | # This is mainly result of experimenting.
143 | # The core idea is that the volume of the object is enlarged slightly
144 | # (by subtracting a constant from the depth map).
145 | # Dilation additionally enlarges thin structures (e.g. for chairs).
146 | depthmap -= self.depth_offset_factor * self.voxel_size
147 | depthmap = ndimage.morphology.grey_erosion(depthmap, size=(3, 3))
148 | depthmaps.append(depthmap)
149 |
150 | if output_path is not None:
151 | write_hdf5(output_path, np.array(depthmaps))
152 | return depthmaps
153 |
154 | def fusion(self, depthmaps, Rs):
155 | """Fuse the rendered depth maps.
156 |
157 | Arguments:
158 | -----------
159 | depthmaps: np.array of depth maps
160 | Rs: rotation matrices
161 | """
162 |
163 | Ks = self.fusion_intrisics.reshape((1, 3, 3))
164 | Ks = np.repeat(Ks, len(depthmaps), axis=0).astype(np.float32)
165 |
166 | Ts = []
167 | for i in range(len(Rs)):
168 | Rs[i] = Rs[i]
169 | Ts.append(np.array([0, 0, 1]))
170 |
171 | Ts = np.array(Ts).astype(np.float32)
172 | Rs = np.array(Rs).astype(np.float32)
173 |
174 | depthmaps = np.array(depthmaps).astype(np.float32)
175 | views = libfusion.PyViews(depthmaps, Ks, Rs, Ts)
176 |
177 | # Note that this is an alias defined as libfusiongpu.tsdf_gpu or
178 | # libfusioncpu.tsdf_cpu!
179 | return compute_tsdf(
180 | views,
181 | self.resolution,
182 | self.resolution,
183 | self.resolution,
184 | self.voxel_size,
185 | self.truncation,
186 | False
187 | )
188 |
189 | def to_watertight(self, mesh, output_path=None, file_type="off"):
190 | # Get the views that we will use for the rendering
191 | Rs = self.get_views()
192 | # Render the depth maps
193 | depths = self.render(mesh, Rs)
194 | tsdf = self.fusion(depths, Rs)[0]
195 | # To ensure that the final mesh is indeed watertight
196 | tsdf = np.pad(tsdf, 1, "constant", constant_values=1e6)
197 | vertices, triangles = mcubes.marching_cubes(-tsdf, 0)
198 | # Remove padding offset
199 | vertices -= 1
200 | # Normalize to [-0.5, 0.5]^3 cube
201 | vertices /= self.resolution
202 | vertices -= 0.5
203 |
204 | tr_mesh = trimesh.Trimesh(vertices=vertices, faces=triangles)
205 | if output_path is not None:
206 | tr_mesh.export(output_path, file_type)
207 | return tr_mesh
208 |
--------------------------------------------------------------------------------
/watertight_transformer/utils.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import h5py
3 |
4 |
5 | def write_hdf5(file, tensor, key = 'tensor'):
6 | """Write a simple tensor, i.e. numpy array ,to HDF5.
7 | """
8 | assert type(tensor) == np.ndarray, 'expects numpy.ndarray'
9 |
10 | h5f = h5py.File(file, 'w')
11 | chunks = list(tensor.shape)
12 | if len(chunks) > 2:
13 | chunks[2] = 1
14 | if len(chunks) > 3:
15 | chunks[3] = 1
16 | if len(chunks) > 4:
17 | chunks[4] = 1
18 |
19 | h5f.create_dataset(
20 | key, data = tensor, chunks = tuple(chunks), compression = 'gzip'
21 | )
22 | h5f.close()
23 |
24 |
25 | def read_hdf5(file, key = 'tensor'):
26 | """Read a tensor, i.e. numpy array, from HDF5.
27 | """
28 | assert os.path.exists(file), 'file %s not found' % file
29 | h5f = h5py.File(file, 'r')
30 | assert key in h5f.keys(), 'key %s not found in file %s' % (key, file)
31 | tensor = h5f[key][()]
32 | h5f.close()
33 | return tensor
34 |
--------------------------------------------------------------------------------