├── .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 | --------------------------------------------------------------------------------