diff --git a/.DS_Store b/.DS_Store index ad74fd0..2702774 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/octree/.DS_Store b/octree/.DS_Store new file mode 100644 index 0000000..4560d82 Binary files /dev/null and b/octree/.DS_Store differ diff --git a/octree/CMakeLists.txt b/octree/CMakeLists.txt new file mode 100644 index 0000000..ba6d98e --- /dev/null +++ b/octree/CMakeLists.txt @@ -0,0 +1,120 @@ +cmake_minimum_required(VERSION 3.8.2) + +# options +option(USE_MINIBALL "Use the Miniball.hpp" ON) +option(USE_WINDOWS_IO "Use the header io.h provided by Windows" OFF) +option(USE_CUDA "CPU only, without CUDA" ON) +option(USE_PYTHON "Build the python interface with pybind11" ON) +option(USE_GLOG "Use glog" OFF) +option(USE_OPENMP "Use OpenMP for speed up" OFF) +option(USE_RPLY "Use the library rply in the project octree" OFF) +set(VCPKG "" CACHE PATH "The VCPKG path, containing glog and gtest") + + +# set languanges +if(USE_CUDA) + project(Octree LANGUAGES CUDA CXX C) + add_definitions(-DUSE_CUDA) + set(CMAKE_CUDA_STANDARD 11) + set(CMAKE_CUDA_STANDARD_REQUIRED ON) +else() + project(Octree LANGUAGES C CXX) +endif() + +# use folder to orgnize the project +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +# # c++11 Support +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# add target_link_libraries +if(USE_MINIBALL) + add_definitions(-DUSE_MINIBALL) + set(miniball_path "${PROJECT_SOURCE_DIR}/external/octree-ext/miniball") + include_directories(${miniball_path}) +endif() + +if(USE_RPLY) + add_definitions(-DUSE_RPLY) + file(GLOB src_rply + "${PROJECT_SOURCE_DIR}/external/octree-ext/rply-1.1.4/*.h" + "${PROJECT_SOURCE_DIR}/external/octree-ext/rply-1.1.4/*.c") + include_directories("${PROJECT_SOURCE_DIR}/external/octree-ext/rply-1.1.4") + add_library(rply ${src_rply}) +endif() + +if(USE_GLOG) + add_definitions(-DUSE_GLOG) +endif() + +if(USE_WINDOWS_IO) + add_definitions(-DUSE_WINDOWS_IO) +endif() + +if(USE_OPENMP) + find_package(OpenMP) + if (OPENMP_FOUND) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") + endif() +endif() + +set(happly_path "${PROJECT_SOURCE_DIR}/external/octree-ext/happly") +include_directories(${happly_path}) + +set(nanoflann_path "${PROJECT_SOURCE_DIR}/external/octree-ext/nanoflann/include") +include_directories(${nanoflann_path}) + + +# files +file(GLOB src_octree_lib + "${PROJECT_SOURCE_DIR}/octree/*.h" + "${PROJECT_SOURCE_DIR}/octree/*.cpp") +if(USE_CUDA) + file(GLOB cuda_octree_lib + "${PROJECT_SOURCE_DIR}/octree/*.cu") + set(src_octree_lib ${src_octree_lib} ${cuda_octree_lib}) +endif() + +file(GLOB src_viewer + "${PROJECT_SOURCE_DIR}/viewer/*.h" + "${PROJECT_SOURCE_DIR}/viewer/*.cpp" ) +file(GLOB src_scanner + "${PROJECT_SOURCE_DIR}/scanner/*.h" + "${PROJECT_SOURCE_DIR}/scanner/*.cpp" ) +file(GLOB src_test + "${PROJECT_SOURCE_DIR}/test/*.cpp") + +# includes +include_directories("${PROJECT_BINARY_DIR}") +include_directories("${PROJECT_SOURCE_DIR}/octree") +include_directories("${PROJECT_SOURCE_DIR}/viewer") +include_directories("${PROJECT_SOURCE_DIR}/scanner") + + +# add the octree lib +add_library(octree_lib ${src_octree_lib}) +target_compile_features(octree_lib PUBLIC cxx_std_11) +if(USE_RPLY) + target_link_libraries(octree_lib rply) +endif(USE_RPLY) + +# add tools +file(GLOB_RECURSE srcs "${PROJECT_SOURCE_DIR}/tools/*.cpp") +foreach(source ${srcs}) + get_filename_component(name ${source} NAME_WE) + if(name MATCHES "build_octree") + set(name "octree") + endif() + add_executable(${name} ${source}) + target_link_libraries(${name} octree_lib) + set_target_properties(${name} PROPERTIES FOLDER "tools") +endforeach(source) + + +# add the python interface +if(USE_PYTHON) + add_subdirectory(python) +endif() \ No newline at end of file diff --git a/octree/external/readme.md b/octree/external/readme.md new file mode 100644 index 0000000..5a7eea9 --- /dev/null +++ b/octree/external/readme.md @@ -0,0 +1,8 @@ +# ReadMe + +This folder contains the external libraries used by the octree project. +These libraries can be downloaded via the following commaneds: + +``` +git clone --recursive https://github.com/wang-ps/octree-ext.git +``` \ No newline at end of file diff --git a/octree/octree/cmd_flags.h b/octree/octree/cmd_flags.h new file mode 100644 index 0000000..365bea1 --- /dev/null +++ b/octree/octree/cmd_flags.h @@ -0,0 +1,194 @@ +#ifndef CMD_FLAGS_H_ +#define CMD_FLAGS_H_ + +#include +#include +#include +#include +#include +#include + +namespace cflags { + +using std::string; +using std::shared_ptr; + +enum Require { kOptional = 0, kRequired}; + +class CmdFlags { + public: + CmdFlags(const string& name, const string& usage, const Require require) + : name_(name), usage_(usage), require_(require), set_(false) {} + virtual const string help() const = 0; + virtual void set_value(const char* argv) = 0; + const string& name() const { return name_; } + const string& usage() const { return usage_; } + const Require& require() const { return require_; } + const bool& set() { return set_; } + + protected: + string name_; + string usage_; + Require require_; + bool set_; +}; + +template +class CmdFlags_Dtype : public CmdFlags { + public: + CmdFlags_Dtype(const string& name, const Dtype& value, const string& usage, + const Require require) : CmdFlags(name, usage, require), value_(value) {} + void set_value(const Dtype& value) { + value_ = value; set_ = true; + } + const Dtype& value() { return value_; } + virtual const string help() const { + std::ostringstream oss; + bool optional = require_ == kOptional; + if (optional) { oss << "\t["; } else { oss << "\t "; } + oss << "--" << name_ << " <" << usage_ << ">"; + if (optional) oss << "=" << value_ << "]"; + oss << "\n"; + return oss.str(); + } + protected: + Dtype value_; +}; + +class CmdFlags_int : public CmdFlags_Dtype { + public: + CmdFlags_int(const string& name, const int& value, const string& usage, + const Require require) : CmdFlags_Dtype(name, value, usage, require) {} + virtual void set_value(const char* argv) { + value_ = atoi(argv); set_ = true; + } +}; + +class CmdFlags_float : public CmdFlags_Dtype { + public: + CmdFlags_float(const string& name, const float& value, const string& usage, + const Require require) : CmdFlags_Dtype(name, value, usage, require) {} + virtual void set_value(const char* argv) { + value_ = static_cast(atof(argv)); set_ = true; + } +}; + +class CmdFlags_bool : public CmdFlags_Dtype { + public: + CmdFlags_bool(const string& name, const bool& value, const string& usage, + const Require require) : CmdFlags_Dtype(name, value, usage, require) {} + virtual void set_value(const char* argv) { + value_ = atoi(argv) != 0; set_ = true; + } +}; + +class CmdFlags_string : public CmdFlags_Dtype { + public: + CmdFlags_string(const string& name, const string& value, const string& usage, + const Require require) : CmdFlags_Dtype(name, value, usage, require) {} + virtual void set_value(const char* argv) { + value_.assign(argv); set_ = true; + } +}; + +class FlagRegistry { + public: + typedef std::map > FlagMap; + + static FlagMap& Registry() { + static std::unique_ptr g_registry_(new FlagMap()); + return *g_registry_; + } + + static void AddFlag(const string& name, shared_ptr flag) { + FlagMap& flag_map = Registry(); + flag_map[name] = flag; + } + + private: + // FlagRegistry should never be instantiated - + // everything is done with its static variables. + FlagRegistry() {} +}; + +class FlagRegisterer { + public: + FlagRegisterer(const string& name, shared_ptr cmd_flag) { + FlagRegistry::AddFlag(name, cmd_flag); + } +}; + +void PrintHelpInfo(const string& info = "") { + std::cout << info << std::endl; + std::vector help_info; + auto& flag_map = FlagRegistry::Registry(); + for (auto& it : flag_map) { + help_info.push_back(it.second->help()); + } + std::sort(help_info.begin(), help_info.end()); + for (auto& it : help_info) { + std::cout << it; + } + std::cout << std::endl; +} + +bool ParseCmd(int argc, char *argv[]) { + // parse + auto& flag_map = FlagRegistry::Registry(); + for (int i = 1; i < argc; i += 2) { + if (argv[i][0] == '-' && argv[i][1] == '-') { + string name(&argv[i][2]); + auto it = flag_map.find(name); + if (it != flag_map.end()) { + if (i + 1 >= argc) { + std::cout << "The parameter " << argv[i] << " is unset!" << std::endl; + return false; + } + it->second->set_value(argv[i + 1]); + //} else if (name == "help") { + // PrintHelpInfo(); + } else { + std::cout << "Unknown cmd parameter: " << argv[i] << std::endl; + return false; + } + } else { + std::cout << "Invalid cmd parameter: " << argv[i] << std::endl; + return false; + } + } + + // check + for (auto& it : flag_map) { + CmdFlags* pflag = it.second.get(); + if (pflag->require() == kRequired && pflag->set() == false) { + std::cout << " The parameter --" << pflag->name() + << " has to be set!\n"; + return false; + } + } + return true; +} +} // namespace cflags + +#define DEFINE_CFLAG_VAR(dtype, name, require, val, usage) \ + namespace cflags{ \ + static FlagRegisterer g_registor_##name(#name, \ + shared_ptr(new CmdFlags_##dtype(#name, val, usage, require))); \ + const dtype& FLAGS_##name = std::dynamic_pointer_cast( \ + FlagRegistry::Registry()[#name])->value(); \ + } \ + using cflags::FLAGS_##name + +#define DEFINE_int(name, require, default_val, usage) \ + DEFINE_CFLAG_VAR(int, name, require, default_val, usage) + +#define DEFINE_float(name, require, default_val, usage) \ + DEFINE_CFLAG_VAR(float, name, require, default_val, usage) + +#define DEFINE_bool(name, require, default_val, usage) \ + DEFINE_CFLAG_VAR(bool, name, require, default_val, usage) + +#define DEFINE_string(name, require, default_val, usage) \ + DEFINE_CFLAG_VAR(string, name, require, default_val, usage) + +#endif // CMD_FLAGS_H_ diff --git a/octree/octree/contour.cpp b/octree/octree/contour.cpp new file mode 100644 index 0000000..c277c72 --- /dev/null +++ b/octree/octree/contour.cpp @@ -0,0 +1,163 @@ +#include "contour.h" +#include "marching_cube.h" + + +void Contour::marching_cube(vector& V, vector& F) { + // subdivide + const int depth = octree_->info().depth(); + vector nodes_subdivided; + for (int d = octree_->info().full_layer(); d < depth; ++d) { + // Check subdividion of octree nodes + int nnum = octree_->info().node_num(d); + vector nodes_need_subdivide; + for (int i = 0; i < nnum; ++i) { + // Only check the leaf nodes + if (octree_->children_cpu(d)[i] < 0) { + uint32 keyi = octree_->key_cpu(d)[i]; + if (check_subdividion(keyi, d)) { + nodes_need_subdivide.push_back(keyi); + } + } + } + + // check the subdivided nodes in the last iteration + for (int i = 0; i < nodes_subdivided.size(); ++i) { + uint32 keyi = nodes_subdivided[i]; + if (check_subdividion(keyi, d)) { + nodes_need_subdivide.push_back(keyi); + } + } + + // subdivide + size_t sz = nodes_need_subdivide.size(); + nodes_subdivided.resize(8 * sz); + for (int i = 0; i < sz; ++i) { + subdivide(nodes_subdivided.data() + 8 * i, nodes_need_subdivide[i]); + } + } + + // marching cube + V.clear(); F.clear(); + nodes_subdivided.insert(nodes_subdivided.end(), + octree_->key_cpu(depth), octree_->key_cpu(depth) + octree_->info().node_num(depth)); + for (int i = 0; i < nodes_subdivided.size(); ++i) { + uint32 weight_case = 0; + float corner_val[8], pt[3]; + octree_->key2xyz(pt, nodes_subdivided[i], depth); + for (int j = 0; j < 8; ++j) { + int x = pt[0] + MarchingCube::corner_[j][0]; + int y = pt[1] + MarchingCube::corner_[j][1]; + int z = pt[2] + MarchingCube::corner_[j][2]; + auto fvalue = fval(x, y, z); + corner_val[j] = fvalue.first; + if (fvalue.second != 0) weight_case |= (1 << j); + } + // only consider the voxel that in the support area of the implicit function + if (weight_case != 255) continue; + + MarchingCube m_cube(corner_val, 0.0f, pt, V.size() / 3); + m_cube.contouring(V, F); + } + + // translate and scale points + const float* bbmin = octree_->info().bbmin(); + const float scale = octree_->info().bbox_max_width() / float(1 << depth); + for (int i = 0; i < V.size() / 3; ++i) { + for (int c = 0; c < 3; ++c) { + V[i * 3 + c] = V[i * 3 + c] * scale + bbmin[c]; + } + } +} + +pair Contour::fval(int x, int y, int z) { + const int depth = octree_->info().depth(); + int key = (x << 2 * depth) | (y << depth) | z; + auto it = fval_map_.find(key); + if (it == fval_map_.end()) { + auto v = value_.fval(x, y, z); + fval_map_[key] = v; // insert new element + return v; + } else { + return it->second; + } +} + +bool Contour::check_subdividion(const uint32 node_key, const int depth) { + // get cooridinates + uint32 xyz[3] = { 0 }; + octree_->key2xyz(xyz, node_key, depth); + int depth_ = octree_->info().depth(); + const int scale = 1 << (depth_ - depth); + for (int c = 0; c < 3; ++c) { xyz[c] *= scale; } + + // check 8 cornors + const uint32 mask[8] = { 1, 2, 4, 8, 16, 32, 64, 128 }; + uint32 cube_case = 0, weight_case = 0; + for (int i = 0; i < 8; ++i) { + int x = xyz[0] + MarchingCube::corner_[i][0] * scale; + int y = xyz[1] + MarchingCube::corner_[i][2] * scale; + int z = xyz[2] + MarchingCube::corner_[i][2] * scale; + + auto fvalue = fval(x, y, z); // pair + if (fvalue.first < 0) cube_case |= mask[i]; + if (fvalue.second != 0) weight_case |= mask[i]; + } + if (cube_case != 0 && cube_case != 255 && weight_case == 255) return true; + + // check 6 faces + const int coord[6][3] = { + {xyz[0], xyz[1], xyz[2]}, {xyz[0] + scale, xyz[1], xyz[2]}, + {xyz[0], xyz[1], xyz[2]}, {xyz[0], xyz[1] + scale, xyz[2]}, + {xyz[0], xyz[1], xyz[2]}, {xyz[0], xyz[1], xyz[2] + scale} + }; + const int axis1[6][3] = { + {0, 1, 0}, {0, 1, 0}, {1, 0, 0}, {1, 0, 0}, {1, 0, 0}, {1, 0, 0} + }; + const int axis2[6][3] = { + {0, 0, 1}, {0, 0, 1}, {0, 0, 1}, {0, 0, 1}, {0, 1, 0}, {0, 1, 0} + }; + for (int i = 0; i < 6; ++i) { + for (int m = 0; m < scale; ++m) { + for (int n = 0; n < scale; ++n) { + uint32 face_case = 0, wt_case = 0; + for (int k = 0; k < 4; ++k) { + int m1 = (k & 1) ? m + 1 : m; + int n1 = (k & 2) ? n + 1 : n; + int x = coord[i][0] + m1 * axis1[i][0] + n1 * axis2[i][0]; + int y = coord[i][1] + m1 * axis1[i][1] + n1 * axis2[i][1]; + int z = coord[i][2] + m1 * axis1[i][2] + n1 * axis2[i][2]; + + auto fvalue = fval(x, y, z); // pair + if (fvalue.first < 0) face_case |= mask[k]; + if (fvalue.second != 0) wt_case |= mask[k]; + + } + if (face_case != 0 && face_case != 15 && wt_case == 15) return true; + } + } + } + + return false; +} + +void Contour::subdivide(uint32* key_output, const uint32 key_input) const { + // !!! caveat: depth should be smaller than 8 + typedef unsigned char ubyte; + if (octree_->info().is_key2xyz()) { + const ubyte* pt = reinterpret_cast(&key_input); + ubyte x = pt[0] << 1; + ubyte y = pt[1] << 1; + ubyte z = pt[2] << 1; + for (int i = 0; i < 8; ++i) { + ubyte* xyz = reinterpret_cast(key_output + i); + xyz[0] = (i & 1) ? x + 1 : x; + xyz[1] = (i & 2) ? y + 1 : y; + xyz[2] = (i & 4) ? z + 1 : z; + } + } else { + uint32 key_in = key_input << 3; + for (int i = 0; i < 8; ++i) { + key_output[i] = key_in | i; + } + } +} \ No newline at end of file diff --git a/octree/octree/contour.h b/octree/octree/contour.h new file mode 100644 index 0000000..3723cbf --- /dev/null +++ b/octree/octree/contour.h @@ -0,0 +1,41 @@ +#ifndef _OCTREE_CONTOUR_ +#define _OCTREE_CONTOUR_ + +#include +#include +#include + +#include "octree_parser.h" +#include "octree_value.h" + +using std::pair; +using std::vector; +using std::unordered_map; + + +class Contour { + public: + Contour(const OctreeParser* oct = nullptr): value_(oct) { + set_octree(oct); + } + void set_octree(const OctreeParser* oct) { + octree_ = oct; + fval_map_.clear(); + } + + void marching_cube(vector& V, vector& F); + + protected: + typedef unsigned int uint32; + pair fval(int x, int y, int z); + bool check_subdividion(const uint32 node_key, const int depth); + void subdivide(uint32* key_output, const uint32 key_input) const; + + protected: + OctreeValue value_; + const OctreeParser* octree_; + unordered_map > fval_map_; + +}; + +#endif \ No newline at end of file diff --git a/octree/octree/device_alternate.h b/octree/octree/device_alternate.h new file mode 100644 index 0000000..00f8b44 --- /dev/null +++ b/octree/octree/device_alternate.h @@ -0,0 +1,46 @@ +#ifndef OCTREE_DEVICE_ALTERNATE_H_ +#define OCTREE_DEVICE_ALTERNATE_H_ + +#include "logs.h" + +#ifdef USE_CUDA + +#include +#include + + +// CUDA: various checks for different function calls. +#define CUDA_CHECK(condition) \ + do { \ + cudaError_t error = condition; \ + CHECK(error == cudaSuccess) << " " << cudaGetErrorString(error); \ + } while (0) + + +// CUDA: grid stride looping +#define CUDA_KERNEL_LOOP(i, n) \ + for (int i = blockIdx.x * blockDim.x + threadIdx.x; \ + i < (n); \ + i += blockDim.x * gridDim.x) + + +// CUDA: check for error after kernel execution and exit loudly if there is one. +#define CUDA_POST_KERNEL_CHECK CUDA_CHECK(cudaPeekAtLastError()) + + +// CUDA: use 512 threads per block +const int kCudaThreadsNum = 512; + +// CUDA: number of blocks for threads. +inline int CudaGetBlocks(const int N) { + return (N + kCudaThreadsNum - 1) / kCudaThreadsNum; +} + + +#else + +#define NO_GPU CHECK(false) << "Cannot use GPU in CPU mode." + +#endif // USE_CUDA + +#endif // OCTREE_DEVICE_ALTERNATE_H_ diff --git a/octree/octree/filenames.cpp b/octree/octree/filenames.cpp new file mode 100644 index 0000000..4ffd0d0 --- /dev/null +++ b/octree/octree/filenames.cpp @@ -0,0 +1,101 @@ +#include "filenames.h" +#include + + +string extract_path(string str) { + std::replace(str.begin(), str.end(), '\\', '/'); + size_t pos = str.rfind('/'); + if (string::npos == pos) { + return string("."); + } else { + return str.substr(0, pos); + } +} + +string extract_filename(string str) { + std::replace(str.begin(), str.end(), '\\', '/'); + size_t pos = str.rfind('/') + 1; + size_t len = str.rfind('.'); + if (string::npos != len) len -= pos; + return str.substr(pos, len); +} + +string extract_suffix(string str) { + string suffix; + size_t pos = str.rfind('.'); + if (pos != string::npos) { + suffix = str.substr(pos + 1); + std::transform(suffix.begin(), suffix.end(), suffix.begin(), tolower); + } + return suffix; +} + + +#if defined _MSC_VER +#include + +void mkdir(const string& dir) { + _mkdir(dir.c_str()); +} + +#elif defined __GNUC__ + +#include +#include + +void mkdir(const string& dir) { + mkdir(dir.c_str(), 0744); +} +#endif + + +#ifdef USE_WINDOWS_IO +#include + +void get_all_filenames(vector& all_filenames, const string& filename_in) { + all_filenames.clear(); + string file_path = extract_path(filename_in) + "/"; + string filename = file_path + "*" + filename_in.substr(filename_in.rfind('.')); + + _finddata_t c_file; + intptr_t hFile = _findfirst(filename.c_str(), &c_file); + do { + if (hFile == -1) break; + all_filenames.push_back(file_path + string(c_file.name)); + } while (_findnext(hFile, &c_file) == 0); + _findclose(hFile); +} + +#else +#include + +void get_all_filenames(vector& all_filenames, const string& data_list) { + all_filenames.clear(); + + std::ifstream infile(data_list); + if (!infile) return; + + string line; + while (std::getline(infile, line)) { + all_filenames.push_back(line); + } + infile.close(); +} + +#endif + + +void get_all_filenames_qq(vector& all_filenames, const string& data_list) { + all_filenames.clear(); + + std::ifstream infile(data_list); + if (!infile) return; + + string line; + while (std::getline(infile, line)) { + std::string token = line.substr(0, line.find(" ")); + all_filenames.push_back(token); + } + infile.close(); +} + diff --git a/octree/octree/filenames.h b/octree/octree/filenames.h new file mode 100644 index 0000000..f2535c5 --- /dev/null +++ b/octree/octree/filenames.h @@ -0,0 +1,19 @@ +#ifndef _OCTREE_FILENAMES_ +#define _OCTREE_FILENAMES_ + +#include +#include + +using std::vector; +using std::string; + +void mkdir(const string& dir); + +string extract_path(string str); +string extract_filename(string str); +string extract_suffix(string str); + +void get_all_filenames(vector& all_filenames, const string& filename); +void get_all_filenames_qq(vector& all_filenames, const string& filename); + +#endif // _OCTREE_FILENAMES_ diff --git a/octree/octree/gemm_engine.h b/octree/octree/gemm_engine.h new file mode 100644 index 0000000..2e4d988 --- /dev/null +++ b/octree/octree/gemm_engine.h @@ -0,0 +1,18 @@ +#ifndef _OCTREE_GEMM_ENGINE_ +#define _OCTREE_GEMM_ENGINE_ + + +namespace octree { + +template +class GEMMEngine { + public: + // C = beta * C + alpha * A * B + virtual void gemm(const bool TransA, const bool TransB, + const int M, const int N, const int K, const Dtype alpha, + const Dtype* A, const Dtype* B, const Dtype beta, Dtype* C) = 0; +}; + +} // namespace octree + +#endif // _OCTREE_GEMM_ENGINE_ diff --git a/octree/octree/logs.h b/octree/octree/logs.h new file mode 100644 index 0000000..958bba1 --- /dev/null +++ b/octree/octree/logs.h @@ -0,0 +1,18 @@ +#ifndef _OCTREE_LOGS_H_ +#define _OCTREE_LOGS_H_ + +#ifdef USE_GLOG + +#include + +#else + +#include + +// do nothing except for evaluating the #expression#, use std::cout to eat the +// following expressions like "<<...<<...;" +#define CHECK(expression) ((void)0), (expression), (true) ? std::cout : std::cout + +#endif // USE_GLOG + +#endif // _OCTREE_LOGS_H_ diff --git a/octree/octree/marching_cube.cpp b/octree/octree/marching_cube.cpp new file mode 100644 index 0000000..9992f95 --- /dev/null +++ b/octree/octree/marching_cube.cpp @@ -0,0 +1,122 @@ +#include "marching_cube.h" + + +inline int MarchingCube::btwhere(int x) const { + float f = (unsigned int)x; + return ((*(unsigned int*)(&f)) >> 23) - 127; +} + +inline void MarchingCube::interpolation(float* pt, const float* pt1, + const float* pt2, const float f1, const float f2) const { + float df = f2 - f1; + if (df == 0) df += 1.0e-10f; + float t = -f1 / df; + for (int i = 0; i < 3; ++i) { + pt[i] = pt1[i] + t * (pt2[i] - pt1[i]); + } +} + +MarchingCube::MarchingCube(const float* fval, float iso_val, const float* left_btm, + int vid) { + set(fval, iso_val, left_btm, vid); +} + +void MarchingCube::set(const float* fval, float iso_val, const float* left_btm, + int vid) { + fval_ = fval; + iso_value_ = iso_val; + left_btm_ = left_btm; + vtx_id_ = vid; +} + +unsigned int MarchingCube::compute_cube_case() const { + const unsigned int mask[8] = { 1, 2, 4, 8, 16, 32, 64, 128 }; + unsigned int cube_case = 0; + for (int i = 0; i < 8; ++i) { + if (fval_[i] < iso_value_) cube_case |= mask[i]; + } + return cube_case; +} + +void MarchingCube::contouring(vector& vtx, vector& face) const { + // compute cube cases + unsigned int cube_case = compute_cube_case(); + + // generate vtx + int vid[12], id = vtx_id_; + int edge_mask = edge_table_[cube_case]; + while (edge_mask != 0) { + int pos = btwhere(edge_mask & (-edge_mask)); + + // set vertex id + vid[pos] = id++; + + // calc points + float pti[3]; + int v1 = edge_vert_[pos][0]; + int v2 = edge_vert_[pos][1]; + interpolation(pti, corner_[v1], corner_[v2], fval_[v1], fval_[v2]); + for (int j = 0; j < 3; ++j) { + float p = pti[j] + left_btm_[j]; + vtx.push_back(p); + } + + edge_mask &= edge_mask - 1; + } + + // generate triangle + const int* tri = tri_table_[cube_case]; + for (int i = 0; i < 16; ++i) { + if (tri[i] == -1) break; + face.push_back(vid[tri[i]]); + } +} + + +void intersect_cube(vector& V, const float* pt, const float* pt_base, + const float* normal) { + // compute f_val + float fval[8] = { 0 }; + for (int k = 0; k < 8; ++k) { + for (int j = 0; j < 3; ++j) { + fval[k] += (MarchingCube::corner_[k][j] + pt_base[j] - pt[j]) * normal[j]; + } + } + + // marching cube + V.clear(); + vector F; + MarchingCube mcube(fval, 0, pt_base, 0); + mcube.contouring(V, F); +} + + +void marching_cube_octree(vector& V, vector& F, const vector& pts, + const vector& pts_ref, const vector& normals) { + int num = pts.size() / 3; + V.clear(); F.clear(); + for (int i = 0; i < num; ++i) { + // get point and normal + int ix3 = i * 3; + float pt[3], pt_ref[3], normal[3]; + for (int j = 0; j < 3; ++j) { + pt_ref[j] = pts_ref[ix3 + j]; // the reference point + pt[j] = pts[ix3 + j] - pt_ref[j]; // the local displacement + normal[j] = normals[ix3 + j]; + } + + // compute f_val + float fval[8] = {0}; + for (int k = 0; k < 8; ++k) { + for (int j = 0; j < 3; ++j) { + fval[k] += (MarchingCube::corner_[k][j] - pt[j]) * normal[j]; + } + } + + // marching cube + int vid = V.size() / 3; + MarchingCube mcube(fval, 0, pt_ref, vid); + mcube.contouring(V, F); + } +} + diff --git a/octree/octree/marching_cube.h b/octree/octree/marching_cube.h new file mode 100644 index 0000000..4a09807 --- /dev/null +++ b/octree/octree/marching_cube.h @@ -0,0 +1,41 @@ +#ifndef MARCHIING_CUBE_H_ +#define MARCHIING_CUBE_H_ + +#include + +using std::vector; + +class MarchingCube { + public: + MarchingCube() : fval_(nullptr), iso_value_(0), left_btm_(nullptr), vtx_id_(0) {} + MarchingCube(const float* fval, float iso_val, const float* left_btm, int vid); + void set(const float* fval, float iso_val, const float* left_btm, int vid); + void contouring(vector& vtx, vector& face) const; + unsigned int compute_cube_case() const; + + private: + inline int btwhere(int x) const; + inline void interpolation(float* pt, const float* pt1, const float* pt2, + const float f1, const float f2) const; + + protected: + const float* fval_; + float iso_value_; + const float* left_btm_; + int vtx_id_; + + public: + static const int edge_table_[256]; + static const int tri_table_[256][16]; + static const int edge_vert_[12][2]; + static const float corner_[8][3]; +}; + + +// two convenient interfaces +void marching_cube_octree(vector& V, vector& F, const vector& pts, + const vector& pts_ref, const vector& normals); +void intersect_cube(vector& V, const float* pt, const float* pt_base, + const float* normal); + +#endif // MARCHIING_CUBE_H_ \ No newline at end of file diff --git a/octree/octree/marching_cube_table.cpp b/octree/octree/marching_cube_table.cpp new file mode 100644 index 0000000..06dbdf2 --- /dev/null +++ b/octree/octree/marching_cube_table.cpp @@ -0,0 +1,321 @@ +#include "marching_cube.h" + + +const int MarchingCube::edge_table_[256] = { + 0x0, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, + 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, + 0x190, 0x99, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, + 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, + 0x230, 0x339, 0x33, 0x13a, 0x636, 0x73f, 0x435, 0x53c, + 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, + 0x3a0, 0x2a9, 0x1a3, 0xaa, 0x7a6, 0x6af, 0x5a5, 0x4ac, + 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, + 0x460, 0x569, 0x663, 0x76a, 0x66, 0x16f, 0x265, 0x36c, + 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, + 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff, 0x3f5, 0x2fc, + 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, + 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55, 0x15c, + 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, + 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc, + 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, + 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, + 0xcc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, + 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, + 0x15c, 0x55, 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, + 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, + 0x2fc, 0x3f5, 0xff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, + 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, + 0x36c, 0x265, 0x16f, 0x66, 0x76a, 0x663, 0x569, 0x460, + 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, + 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa, 0x1a3, 0x2a9, 0x3a0, + 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, + 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33, 0x339, 0x230, + 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, + 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99, 0x190, + 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, + 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 +}; + +const int MarchingCube::tri_table_[256][16] = { + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1 }, + { 8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1 }, + { 3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1 }, + { 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, + { 4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1 }, + { 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1 }, + { 10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1 }, + { 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, + { 5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1 }, + { 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1 }, + { 2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1 }, + { 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1 }, + { 11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1 }, + { 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1 }, + { 11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1 }, + { 11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1 }, + { 2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1 }, + { 6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1 }, + { 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1 }, + { 6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1 }, + { 6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1 }, + { 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1 }, + { 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1 }, + { 3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1 }, + { 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1 }, + { 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1 }, + { 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1 }, + { 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1 }, + { 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1 }, + { 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1 }, + { 10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1 }, + { 1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1 }, + { 0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1 }, + { 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1 }, + { 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1 }, + { 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1 }, + { 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1 }, + { 3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1 }, + { 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1 }, + { 10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, + { 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1 }, + { 7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, + { 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1 }, + { 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1 }, + { 11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1 }, + { 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1 }, + { 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1 }, + { 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1 }, + { 7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1 }, + { 10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1 }, + { 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1 }, + { 7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1 }, + { 6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1 }, + { 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1 }, + { 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1 }, + { 8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1 }, + { 1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1 }, + { 10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1 }, + { 10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1 }, + { 9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1 }, + { 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1 }, + { 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1 }, + { 7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1 }, + { 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1 }, + { 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1 }, + { 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1 }, + { 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1 }, + { 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1 }, + { 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1 }, + { 6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1 }, + { 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1 }, + { 6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1 }, + { 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1 }, + { 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1 }, + { 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1 }, + { 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1 }, + { 9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1 }, + { 1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1 }, + { 10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1 }, + { 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1 }, + { 11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1 }, + { 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1 }, + { 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1 }, + { 2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1 }, + { 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1 }, + { 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1 }, + { 1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1 }, + { 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1 }, + { 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1 }, + { 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1 }, + { 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1 }, + { 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1 }, + { 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1 }, + { 5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1 }, + { 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1 }, + { 8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1 }, + { 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1 }, + { 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1 }, + { 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1 }, + { 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1 }, + { 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1 }, + { 11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1 }, + { 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1 }, + { 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1 }, + { 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1 }, + { 1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1 }, + { 4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1 }, + { 0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1 }, + { 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 } +}; + +int tri_table_count[256] = { + 0, 3, 3, 6, 3, 6, 6, 9, 3, 6, 6, 9, 6, 9, 9, 6, 3, 6, 6, 9, 6, 9, 9, 12, 6, 9, + 9, 12, 9, 12, 12, 9, 3, 6, 6, 9, 6, 9, 9, 12, 6, 9, 9, 12, 9, 12, 12, 9, 6, 9, + 9, 6, 9, 12, 12, 9, 9, 12, 12, 9, 12, 15, 15, 6, 3, 6, 6, 9, 6, 9, 9, 12, 6, 9, + 9, 12, 9, 12, 12, 9, 6, 9, 9, 12, 9, 12, 12, 15, 9, 12, 12, 15, 12, 15, 15, 12, + 6, 9, 9, 12, 9, 12, 6, 9, 9, 12, 12, 15, 12, 15, 9, 6, 9, 12, 12, 9, 12, 15, 9, + 6, 12, 15, 15, 12, 15, 6, 12, 3, 3, 6, 6, 9, 6, 9, 9, 12, 6, 9, 9, 12, 9, 12, 12, + 9, 6, 9, 9, 12, 9, 12, 12, 15, 9, 6, 12, 9, 12, 9, 15, 6, 6, 9, 9, 12, 9, 12, + 12, 15, 9, 12, 12, 15, 12, 15, 15, 12, 9, 12, 12, 9, 12, 15, 15, 12, 12, 9, 15, + 6, 15, 12, 6, 3, 6, 9, 9, 12, 9, 12, 12, 15, 9, 12, 12, 15, 6, 9, 9, 6, 9, 12, 12, + 15, 12, 15, 15, 6, 12, 9, 15, 12, 9, 6, 12, 3, 9, 12, 12, 15, 12, 15, 9, 12, + 12, 15, 15, 6, 9, 12, 6, 3, 6, 9, 9, 6, 9, 12, 6, 3, 9, 6, 12, 3, 6, 3, 3, 0 +}; + +const int MarchingCube::edge_vert_[12][2] = { + { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 0 }, + { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 4 }, + { 0, 4 }, { 1, 5 }, { 2, 6 }, { 3, 7 }, +}; + +const float MarchingCube::corner_[8][3] = { + { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f }, { 0.0f, 1.0f, 1.0f }, { 0.0f, 1.0f, 0.0f }, + { 1.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 1.0f }, { 1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 0.0f } +}; \ No newline at end of file diff --git a/octree/octree/math_functions.cpp b/octree/octree/math_functions.cpp new file mode 100644 index 0000000..7a4f45c --- /dev/null +++ b/octree/octree/math_functions.cpp @@ -0,0 +1,272 @@ +#include "math_functions.h" +#include +#include + +#ifdef USE_MINIBALL +#include + +void bounding_sphere(float& radius, float* center, const float* pt, const int npt) { + const int dim = 3; + radius = 1.0e-10f; // !!! avoid zero radius + center[0] = center[1] = center[2] = 0; + if (npt < 1) return; + + // mini-ball + const float** ap = new const float*[npt]; + for (int i = 0; i < npt; ++i) { ap[i] = pt + dim * i; } + typedef const float** PointIterator; + typedef const float* CoordIterator; + Miniball::Miniball > + miniball(dim, ap, ap + npt); + + // get result + if (miniball.is_valid()) { + const float* cnt = miniball.center(); + for (int i = 0; i < dim; ++i) { + center[i] = cnt[i]; + } + radius += sqrtf(miniball.squared_radius()); + } else { + // the miniball might fail sometimes + // if so, just calculate the bounding box + float bbmin[3] = { 0.0f, 0.0f, 0.0f }; + float bbmax[3] = { 0.0f, 0.0f, 0.0f }; + bouding_box(bbmin, bbmax, pt, npt); + + for (int j = 0; j < dim; ++j) { + center[j] = (bbmax[j] + bbmin[j]) / 2.0f; + float width = (bbmax[j] - bbmin[j]) / 2.0f; + radius += width * width; + } + + radius = sqrtf(radius); + } + + // release + delete[] ap; +} + +#else + +void bounding_sphere(float& radius, float* center, const float* pt, const int npt) { + float bb[3][2] = { + { FLT_MAX, -FLT_MAX }, { FLT_MAX, -FLT_MAX }, { FLT_MAX, -FLT_MAX } + }; + int id[6]; + for (int i = 0; i < 3 * npt; i += 3) { + if (pt[i] < bb[0][0]) { + id[0] = i; bb[0][0] = pt[i]; + } + if (pt[i] > bb[0][1]) { + id[1] = i; bb[0][1] = pt[i]; + } + if (pt[i + 1] < bb[1][0]) { + id[2] = i; bb[1][0] = pt[i + 1]; + } + if (pt[i + 1] > bb[1][1]) { + id[3] = i; bb[1][1] = pt[i + 1]; + } + if (pt[i + 2] < bb[2][0]) { + id[4] = i; bb[2][0] = pt[i + 2]; + } + if (pt[i + 2] > bb[2][1]) { + id[5] = i; bb[2][1] = pt[i + 2]; + } + } + + radius = 0; + int choose_id = -1; + for (int i = 0; i < 3; i++) { + float dx = pt[id[2 * i]] - pt[id[2 * i + 1]]; + float dy = pt[id[2 * i] + 1] - pt[id[2 * i + 1] + 1]; + float dz = pt[id[2 * i] + 2] - pt[id[2 * i + 1] + 2]; + float r2 = dx * dx + dy * dy + dz * dz; + if (r2 > radius) { + radius = r2; choose_id = 2 * i; + } + } + center[0] = 0.5f * (pt[id[choose_id]] + pt[id[choose_id + 1]]); + center[1] = 0.5f * (pt[id[choose_id] + 1] + pt[id[choose_id + 1] + 1]); + center[2] = 0.5f * (pt[id[choose_id] + 2] + pt[id[choose_id + 1] + 2]); + + float radius2 = radius * 0.25f; + radius = sqrtf(radius2); + + for (int i = 0; i < 3 * npt; i += 3) { + float dx = pt[i] - center[0], dy = pt[i + 1] - center[1], dz = pt[i + 2] - center[2]; + float dis2 = dx * dx + dy * dy + dz * dz; + if (dis2 > radius2) { + float old_to_p = sqrt(dis2); + radius = (radius + old_to_p) * 0.5f; + radius2 = radius * radius; + float old_to_new = old_to_p - radius; + center[0] = (radius * center[0] + old_to_new * pt[i]) / old_to_p; + center[1] = (radius * center[1] + old_to_new * pt[i + 1]) / old_to_p; + center[2] = (radius * center[2] + old_to_new * pt[i + 2]) / old_to_p; + } + } +} + +#endif + + +void bouding_box(float* bbmin, float* bbmax, const float* pt, const int npt) { + const int dim = 3; + if (npt < 1) return; + for (int i = 0; i < 3; ++i) { + bbmin[i] = bbmax[i] = pt[i]; + } + + for (int i = 1; i < npt; ++i) { + int i3 = i * 3; + for (int j = 0; j < dim; ++j) { + float tmp = pt[i3 + j]; + if (tmp < bbmin[j]) bbmin[j] = tmp; + if (tmp > bbmax[j]) bbmax[j] = tmp; + } + } +} + +void rotation_matrix(float* rot, const float angle, const float* axis) { + float cosa = cos(angle); // angle in radian + float cosa1 = 1 - cosa; + float sina = sin(angle); + + rot[0] = cosa + axis[0] * axis[0] * cosa1; + rot[1] = axis[0] * axis[1] * cosa1 + axis[2] * sina; + rot[2] = axis[0] * axis[2] * cosa1 - axis[1] * sina; + + rot[3] = axis[0] * axis[1] * cosa1 - axis[2] * sina; + rot[4] = cosa + axis[1] * axis[1] * cosa1; + rot[5] = axis[1] * axis[2] * cosa1 + axis[0] * sina; + + rot[6] = axis[0] * axis[2] * cosa1 + axis[1] * sina; + rot[7] = axis[1] * axis[2] * cosa1 - axis[0] * sina; + rot[8] = cosa + axis[2] * axis[2] * cosa1; +} + +void rotation_matrix(float* rot, const float* angle) { + float cosx = cos(angle[0]), sinx = sin(angle[0]); + float cosy = cos(angle[1]), siny = sin(angle[1]); + float cosz = cos(angle[2]), sinz = sin(angle[2]); + const float rotx[9] = { 1.0f, 0, 0, 0, cosx, sinx, 0, -sinx, cosx }; + const float roty[9] = { cosy, 0, -siny, 0, 1.0f, 0, siny, 0, cosy }; + const float rotz[9] = { cosz, sinz, 0, -sinz, cosz, 0, 0, 0, 1.0f }; + float tmp[9]; + matrix_prod(tmp, rotx, roty, 3, 3, 3); + matrix_prod(rot, tmp, rotz, 3, 3, 3); +} + + +void rotation_matrix(float* rot, const float* axis0, const float* axis1) { + float angle = dot_prod(axis0, axis1); + if (angle < -1) angle = -1; + if (angle > 1) angle = 1; + angle = acos(angle); + + float axis[3]; + cross_prod(axis, axis0, axis1); + float ilen = 1.0 / (norm2(axis, 3) + 1.0e-10); + for (int i = 0; i < 3; ++i) { axis[i] *= ilen; } + + rotation_matrix(rot, angle, axis); +} + +void cross_prod(float * c, const float * a, const float * b) { + c[0] = a[1] * b[2] - a[2] * b[1]; + c[1] = a[2] * b[0] - a[0] * b[2]; + c[2] = a[0] * b[1] - a[1] * b[0]; +} + +float dot_prod(const float * a, const float * b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +void matrix_prod(float* C, const float* A, const float* B, + const int M, const int N, const int K) { + #pragma omp parallel for + for (int n = 0; n < N; ++n) { + for (int m = 0; m < M; ++m) { + C[n * M + m] = 0; + for (int k = 0; k < K; ++k) { + C[n * M + m] += A[k * M + m] * B[n * K + k]; + } + } + } +} + +void axes(float * x, float * y, const float * z) { + // Find the rotation matrix that rotate the vector (0, 0, 1) to z, + // then the first and second colomns of the rotation matrix are the output + float z0[3] = { 0, 0, 1 }, rot[9]; + rotation_matrix(rot, z0, z); + + for (int i = 0; i < 3; ++i) { + x[i] = rot[i]; + y[i] = rot[3 + i]; + } +} + +void inverse_transpose_3x3(float* const out, const float* const mat) { + double a = mat[4] * mat[8] - mat[7] * mat[5]; + double b = mat[3] * mat[8] - mat[5] * mat[6]; + double c = mat[3] * mat[7] - mat[4] * mat[6]; + + double det = mat[0] * a - mat[1] * b + mat[2] * c; + double invdet = 1 / det; + + out[0] = a * invdet; + out[3] = -(mat[1] * mat[8] - mat[2] * mat[7]) * invdet; + out[6] = (mat[1] * mat[5] - mat[2] * mat[4]) * invdet; + out[1] = -b * invdet; + out[4] = (mat[0] * mat[8] - mat[2] * mat[6]) * invdet; + out[7] = -(mat[0] * mat[5] - mat[3] * mat[2]) * invdet; + out[2] = c * invdet; + out[5] = -(mat[0] * mat[7] - mat[6] * mat[1]) * invdet; + out[8] = (mat[0] * mat[4] - mat[3] * mat[1]) * invdet; +} + +bool almost_equal_3x3(const float* const mat1, const float* const mat2) { + for (int i = 0; i < 9; ++i) { + float diff = fabsf(mat1[i] - mat2[i]); + float a = fabsf(mat1[i]); + float b = fabsf(mat2[i]); + float largest = (b > a) ? b : a; + if (diff > largest * FLT_EPSILON) { + return false; + } + } + return true; +} + +void normalize_nx3(float* const pts, int npt) { + const int dim = 3; + for (int i = 0; i < npt; ++i) { + int ix3 = i * dim; + float inv_mag = 1.0f / (norm2(pts + ix3, dim) + 1.0e-15f); + for (int m = 0; m < dim; ++m) { + pts[ix3 + m] = pts[ix3 + m] * inv_mag; + } + } +} + +float norm2(const vector& vec) { + return norm2(vec.data(), vec.size()); +} + +float norm2(const float* vec, int n) { + float len = 0.0f; + for (int i = 0; i < n; ++i) { + len += vec[i] * vec[i]; + } + return sqrtf(len); +} + +template +Dtype clamp(Dtype val, Dtype val_min, Dtype val_max) { + if (val < val_min) val = val_min; + if (val > val_max) val = val_max; + return val; +} +template int clamp(int val, int val_min, int val_max); +template float clamp(float val, float val_min, float val_max); diff --git a/octree/octree/math_functions.h b/octree/octree/math_functions.h new file mode 100644 index 0000000..743760a --- /dev/null +++ b/octree/octree/math_functions.h @@ -0,0 +1,42 @@ +#ifndef _OCTREE_MATH_FUNCTIONS_ +#define _OCTREE_MATH_FUNCTIONS_ + +#include + +using std::vector; + +const float ESP = 1.0e-30f; + +void bounding_sphere(float& radius, float* center, const float* pt, const int npt); +void bouding_box(float* bbmin, float* bbmax, const float* pt, const int npt); + +// !!! The matrix in this header is in column-major storage order !!! + +// Calculate the matrix *rot* given the rotation *axis* and rotation *angle* +void rotation_matrix(float* rot, const float angle, const float* axis); +// Calculate the matrix *rot* given three rotation angles +void rotation_matrix(float* rot, const float* angle); +// The rotation matrix that rotates axis0 to axis1 with minimal angle +void rotation_matrix(float* rot, const float* axis0, const float* axis1); + +void cross_prod(float* c, const float* a, const float* b); +float dot_prod(const float* a, const float* b); +// Inplace product is not allowed: the pointer C must not be equal to A or B +void matrix_prod(float* C, const float* A, const float* B, const int M, + const int N, const int K); + +// Give the z axis, output the x and y axis +void axes(float* x, float* y, const float* z); + +void inverse_transpose_3x3(float* const out, const float* const mat); +bool almost_equal_3x3(const float* const mat1, const float* const mat2); +void normalize_nx3(float* const pts, int npt); + +float norm2(const vector& vec); +float norm2(const float* vec, int n); + +template +Dtype clamp(Dtype val, Dtype val_min, Dtype val_max); + + +#endif // _OCTREE_MATH_FUNCTIONS_ diff --git a/octree/octree/merge_octrees.cpp b/octree/octree/merge_octrees.cpp new file mode 100644 index 0000000..01c50f6 --- /dev/null +++ b/octree/octree/merge_octrees.cpp @@ -0,0 +1,196 @@ +#include "merge_octrees.h" +#include "octree_nn.h" +#include "logs.h" + +void merge_octrees(vector& octree_out, const vector octrees_in) { + MergeOctrees mo; + mo.init(octrees_in); + mo.check_input(); + mo.calc_node_num(); + mo.set_batch_info(); + mo.set_batch_parser(octree_out); + mo.merge_octree(); +} + + +void MergeOctrees::init(const vector& octrees) { + batch_size_ = octrees.size(); + octree_parsers_.resize(batch_size_); + for (int i = 0; i < batch_size_; ++i) { + octree_parsers_[i].set_cpu(octrees[i]); + } + depth_ = octree_parsers_[0].info().depth(); + full_layer_ = octree_parsers_[0].info().full_layer(); +} + + +void MergeOctrees::check_input() { + string err_msg; + bool valid = octree_parsers_[0].info().check_format(err_msg); + CHECK(valid) << err_msg; + for (int i = 1; i < batch_size_; ++i) { + valid = octree_parsers_[i].info().check_format(err_msg); + CHECK(valid) << err_msg; + CHECK(octree_parsers_[0].info().is_consistent(octree_parsers_[i].info())) + << "The formats of input octrees are not consistent, check the database"; + } +} + +void MergeOctrees::calc_node_num() { + // node and non-empty node number in each octree + int sz = (depth_ + 1) * batch_size_; + nnum_.resize(sz), nnum_nempty_.resize(sz); + for (int i = 0; i < batch_size_; ++i) { + for (int d = 0; d < depth_ + 1; ++d) { + int p = i * (depth_ + 1) + d; + nnum_[p] = octree_parsers_[i].info().node_num(d); + nnum_nempty_[p] = octree_parsers_[i].info().node_num_nempty(d); + } + } + + // cumulative node and non-empty node number in each layers + sz = (depth_ + 1) * (batch_size_ + 1); + nnum_cum_layer_.resize(sz), nnum_cum_nempty_layer_.resize(sz); + for (int d = 0; d < depth_ + 1; ++d) { + nnum_cum_layer_[d] = 0; + nnum_cum_nempty_layer_[d] = 0; + for (int i = 0; i < batch_size_; ++i) { + int p = i * (depth_ + 1) + d; + int q = p + depth_ + 1; + nnum_cum_layer_[q] = nnum_[p] + nnum_cum_layer_[p]; + nnum_cum_nempty_layer_[q] = nnum_nempty_[p] + nnum_cum_nempty_layer_[p]; + } + } + + // cumulative node number for each octree + sz = (depth_ + 1) * batch_size_; + nnum_cum_octree_.resize(sz); + for (int i = 0; i < batch_size_; ++i) { + nnum_cum_octree_[i * (depth_ + 1)] = 0; + for (int d = 0; d < depth_; ++d) { + int p = i * (depth_ + 1) + d; + nnum_cum_octree_[p + 1] = nnum_cum_octree_[p] + nnum_[p]; + } + } + + // node and non-empty node number of the batch + nnum_batch_.resize(depth_ + 1), nnum_nempty_batch_.resize(depth_ + 1); + for (int d = 0; d < depth_ + 1; ++d) { + int p = batch_size_ * (depth_ + 1) + d; + nnum_batch_[d] = nnum_cum_layer_[p]; + nnum_nempty_batch_[d] = nnum_cum_nempty_layer_[p]; + } + + // cumulative node number of the batch + nnum_cum_batch_.resize(depth_ + 2); + nnum_cum_batch_[0] = 0; + for (int d = 0; d < depth_ + 1; ++d) { + nnum_cum_batch_[d + 1] = nnum_cum_batch_[d] + nnum_batch_[d]; + } +} + +void MergeOctrees::set_batch_info() { + /// set the octinfo + info_batch_ = octree_parsers_[0].info(); + info_batch_.set_batch_size(batch_size_); + // add the neighbor property + const int kNeighChannel = 8; + info_batch_.set_property(OctreeInfo::kNeigh, kNeighChannel, -1); + // update nodenumber + info_batch_.set_nnum(nnum_batch_.data()); + info_batch_.set_nempty(nnum_nempty_batch_.data()); + info_batch_.set_nnum_cum(); + info_batch_.set_ptr_dis(); + //bool valid = info_batch.check_format(err_msg); + //CHECK(valid) << err_msg; +} + +void MergeOctrees::set_batch_parser(vector& octree_out) { + int sz = info_batch_.sizeof_octree(); + octree_out.resize(sz); + octbatch_parser_.set_cpu(octree_out.data(), &info_batch_); +} + +void MergeOctrees::merge_octree() { + //omp_set_num_threads(8); + //#pragma omp parallel for + for (int i = 0; i < batch_size_; ++i) { + // copy key + // the channel and location of key is 1 and -1 (todo: !!! channel 2 for deeper key) + for (int d = 0; d < depth_ + 1; ++d) { + if (!info_batch_.has_property(OctreeInfo::kKey)) break; + int p = i * (depth_ + 1) + d; + uint32* des = octbatch_parser_.mutable_key_cpu(d) + nnum_cum_layer_[p]; + const uint32* src = octree_parsers_[i].key_cpu(d); + for (int j = 0; j < nnum_[p]; ++j) { + des[j] = src[j]; + // !!! todo: deal with octree depth > 8 + unsigned char* ptr = reinterpret_cast(des + j); + ptr[3] = i; + } + } + + // copy children + // by default, the channel and location of children is 1 and -1, + for (int d = 0; d < depth_ + 1; ++d) { + if (!info_batch_.has_property(OctreeInfo::kChild)) break; + int p = i * (depth_ + 1) + d; + int* des = octbatch_parser_.mutable_children_cpu(d) + nnum_cum_layer_[p]; + const int* src = octree_parsers_[i].children_cpu(d); + for (int j = 0; j < nnum_[p]; ++j) { + des[j] = -1 == src[j] ? src[j] : src[j] + nnum_cum_nempty_layer_[p]; + } + } + + // copy data: !NOTE! the type of signal is float!!! + int feature_channel = info_batch_.channel(OctreeInfo::kFeature); + int feature_location = info_batch_.locations(OctreeInfo::kFeature); + int depth_start = feature_location == depth_ ? depth_ : 0; + for (int d = depth_start; d < depth_ + 1; ++d) { + if (!info_batch_.has_property(OctreeInfo::kFeature)) break; + int p = i * (depth_ + 1) + d; + for (int c = 0; c < feature_channel; c++) { + float* des = octbatch_parser_.mutable_feature_cpu(d) + c * nnum_batch_[d] + nnum_cum_layer_[p]; + const float* src = octree_parsers_[i].feature_cpu(d) + c * nnum_[p]; + for (int j = 0; j < nnum_[p]; ++j) { des[j] = src[j]; } + } + } + + // copy label: !NOTE! the type of label is float!!! + int label_location = info_batch_.locations(OctreeInfo::kLabel); + depth_start = label_location == depth_ ? depth_ : 0; + for (int d = depth_start; d < depth_ + 1; ++d) { + if (!info_batch_.has_property(OctreeInfo::kLabel)) break; + int p = i * (depth_ + 1) + d; + float* des = octbatch_parser_.mutable_label_cpu(d) + nnum_cum_layer_[p]; + const float* src = octree_parsers_[i].label_cpu(d); + for (int j = 0; j < nnum_[p]; ++j) { des[j] = src[j]; } + } + + // copy split label: !NOTE! the type of label is float!!! + int split_location = info_batch_.locations(OctreeInfo::kSplit); + depth_start = split_location == depth_ ? depth_ : 0; + for (int d = depth_start; d < depth_ + 1; ++d) { + if (!info_batch_.has_property(OctreeInfo::kSplit)) break; + int p = i * (depth_ + 1) + d; + float* des = octbatch_parser_.mutable_split_cpu(d) + nnum_cum_layer_[p]; + const float* src = octree_parsers_[i].split_cpu(d); + for (int j = 0; j < nnum_[p]; ++j) des[j] = src[j]; + } + } + + // calc and set neighbor info + for (int d = 1; d < depth_ + 1; ++d) { + if (!info_batch_.has_property(OctreeInfo::kNeigh)) break; + CHECK(info_batch_.has_property(OctreeInfo::kChild)); + + if (d <= full_layer_) { + calc_neigh_cpu(octbatch_parser_.mutable_neighbor_cpu(d), d, batch_size_); + } else { + calc_neigh_cpu(octbatch_parser_.mutable_neighbor_cpu(d), + octbatch_parser_.neighbor_cpu(d - 1), + octbatch_parser_.children_cpu(d - 1), + octbatch_parser_.info().node_num(d - 1)); + } + } +} diff --git a/octree/octree/merge_octrees.h b/octree/octree/merge_octrees.h new file mode 100644 index 0000000..b5a1367 --- /dev/null +++ b/octree/octree/merge_octrees.h @@ -0,0 +1,70 @@ +#ifndef _OCTREE_MERGE_OCTREES_ +#define _OCTREE_MERGE_OCTREES_ + +#include +#include + +#include "octree_parser.h" + +using std::vector; +using std::string; +typedef uint32_t uint32; + +void merge_octrees(vector& octree_out, const vector octrees_in); + + +// A simple implementation of index matrix +class Index { + public: + Index(int col, int row) { reset(col, row); } + + void reset(int col, int row) { + row_ = row; + col_ = col; + data_.assign(row_ * col_, 0); + } + + int operator()(int c, int r) const { + return data_[row_ * c + r]; + } + + int& operator()(int c, int r) { + return data_[row_ * c + r]; + } + + protected: + int row_, col_; + vector data_; +}; + + +class MergeOctrees { + public: + void init(const vector& octrees); + void check_input(); + void calc_node_num(); + void set_batch_info(); + void set_batch_parser(vector& octree_out); + void merge_octree(); + + private: + int depth_; + int full_layer_; + int batch_size_; + vector octree_parsers_; + + vector nnum_; + vector nnum_nempty_; + vector nnum_cum_layer_; + vector nnum_cum_nempty_layer_; + vector nnum_cum_octree_; + vector nnum_batch_; + vector nnum_nempty_batch_; + vector nnum_cum_batch_; + + OctreeInfo info_batch_; + OctreeParser octbatch_parser_; +}; + + +#endif // _OCTREE_MERGE_OCTREES_ diff --git a/octree/octree/mesh.cpp b/octree/octree/mesh.cpp new file mode 100644 index 0000000..c26f8bf --- /dev/null +++ b/octree/octree/mesh.cpp @@ -0,0 +1,448 @@ +#include "mesh.h" + +#include +#include +#include +#include + +#include "math_functions.h" +#include "filenames.h" + +bool read_mesh(const string& filename, vector& V, vector& F) { + bool succ = false; + string suffix = extract_suffix(filename); + if (suffix == "obj") { + succ = read_obj(filename, V, F); + } else if (suffix == "off") { + succ = read_off(filename, V, F); + } else if (suffix == "ply") { + succ = read_ply(filename, V, F); + } else { + //cout << "Error : Unsupported file formate!" << endl; + } + return succ; +} + + +bool write_mesh(const string& filename, const vector& V, const vector& F) { + bool succ = false; + string suffix = extract_suffix(filename); + if (suffix == "obj") { + succ = write_obj(filename, V, F); + } else if (suffix == "off") { + //succ = write_off(filename, V, F); //todo + } else if (suffix == "ply") { + succ = write_ply(filename, V, F); + } else { + //cout << "Error : Unsupported file formate!" << endl; + } + return succ; +} + + +bool read_obj(const string& filename, vector& V, vector& F) { + std::ifstream infile(filename, std::ifstream::binary); + if (!infile) { + //std::cout << "Open OBJ file error!" << std::endl; + return false; + } + + // get length of file + infile.seekg(0, infile.end); + int len = infile.tellg(); + infile.seekg(0, infile.beg); + + // load the file into memory + char* buffer = new char[len + 1]; + infile.read(buffer, len); + buffer[len] = 0; + infile.close(); + + // parse buffer data + vector pVline, pFline; + char* pch = strtok(buffer, "\n"); + while (pch != nullptr) { + if (pch[0] == 'v' && pch[1] == ' ') { + pVline.push_back(pch + 2); + } else if (pch[0] == 'f' && pch[1] == ' ') { + pFline.push_back(pch + 2); + } + + pch = strtok(nullptr, "\n"); + } + + // load V + V.resize(3 * pVline.size()); + //#pragma omp parallel for + for (int i = 0; i < pVline.size(); i++) { + //!!! strtok() is not thread safe in some platforms + char* p = strtok(pVline[i], " "); + for (int j = 0; j < 3; j++) { + V[3 * i + j] = atof(p); + p = strtok(nullptr, " "); + } + } + + // load F + F.resize(3 * pFline.size()); + //#pragma omp parallel for + for (int i = 0; i < pFline.size(); i++) { + char* p = strtok(pFline[i], " "); + for (int j = 0; j < 3; j++) { + F[3 * i + j] = atoi(p) - 1; + p = strtok(nullptr, " "); + } + } + + // release + delete[] buffer; + return true; +} + +bool write_obj(const string& filename, const vector& V, const vector& F) { + std::ofstream outfile(filename, std::ios::binary); + if (!outfile) return false; + + int nv = V.size() / 3; + int nf = F.size() / 3; + if (V.size() % 3 != 0 || F.size() % 3 != 0) return false; + const int len = 64; + char* buffer = new char[(nv + nf) * len]; + + // convert to string + char* pV = buffer; + #pragma omp parallel for + for (int i = 0; i < nv; i++) { + int ix3 = i * 3; + sprintf(pV + i * len, "v %.6g %.6g %.6g\n", V[ix3], V[ix3 + 1], V[ix3 + 2]); + } + + char* pF = buffer + nv * len; + #pragma omp parallel for + for (int i = 0; i < nf; i++) { + int ix3 = i * 3; + sprintf(pF + i * len, "f %d %d %d\n", F[ix3] + 1, F[ix3 + 1] + 1, F[ix3 + 2] + 1); + } + + // shrink + int k = 0; + for (int i = 0; i < nv; i++) { + for (int j = len * i; j < len * (i + 1); j++) { + if (pV[j] == 0) break; + buffer[k++] = pV[j]; + } + } + for (int i = 0; i < nf; i++) { + for (int j = len * i; j < len * (i + 1); j++) { + if (pF[j] == 0) break; + buffer[k++] = pF[j]; + } + } + + // write into file + outfile.write(buffer, k); + + // close file + outfile.close(); + delete[] buffer; + return true; +} + + +bool read_off(const string& filename, vector& V, vector& F) { + std::ifstream infile(filename, std::ios::binary); + if (!infile) { + //std::cout << "Open " + filename + " error!" << std::endl; + return false; + } + + // face/vertex number + int nv, nf, ne; + char head[256]; + infile >> head; // eat head + if (head[0] == 'O' && head[1] == 'F' && head[2] == 'F') { + if (head[3] == 0) { + infile >> nv >> nf >> ne; + } else if (head[3] == ' ') { + vector tokens; + char* pch = strtok(head + 3, " "); + while (pch != nullptr) { + tokens.push_back(pch); + pch = strtok(nullptr, " "); + } + if (tokens.size() != 3) { + //std::cout << filename + " is not an OFF file!" << std::endl; + return false; + } + nv = atoi(tokens[0]); + nf = atoi(tokens[1]); + ne = atoi(tokens[2]); + } else { + //std::cout << filename + " is not an OFF file!" << std::endl; + return false; + } + } else { + //std::cout << filename + " is not an OFF file!" << std::endl; + return false; + } + + // get length of file + int p1 = infile.tellg(); + infile.seekg(0, infile.end); + int p2 = infile.tellg(); + infile.seekg(p1, infile.beg); + int len = p2 - p1; + + // load the file into memory + char* buffer = new char[len + 1]; + infile.read(buffer, len); + buffer[len] = 0; + + // close file + infile.close(); + + // parse buffer data + std::vector pV; + pV.reserve(3 * nv); + char* pch = strtok(buffer, " \r\n"); + pV.push_back(pch); + for (int i = 1; i < 3 * nv; i++) { + pch = strtok(nullptr, " \r\n"); + pV.push_back(pch); + } + std::vector pF; + pF.reserve(3 * nf); + for (int i = 0; i < nf; i++) { + // eat the first data + pch = strtok(nullptr, " \r\n"); + for (int j = 0; j < 3; j++) { + pch = strtok(nullptr, " \r\n"); + pF.push_back(pch); + } + } + + // load vertex + V.resize(3 * nv); + float* p = V.data(); + #pragma omp parallel for + for (int i = 0; i < 3 * nv; i++) { + *(p + i) = atof(pV[i]); + } + + // load face + F.resize(3 * nf); + int* q = F.data(); + #pragma omp parallel for + for (int i = 0; i < 3 * nf; i++) { + *(q + i) = atoi(pF[i]); + } + + //release + delete[] buffer; + return true; +} + + +#ifdef USE_RPLY +#include + +bool read_ply(const string& filename, vector& V, vector& F) { + // open ply file + p_ply ply = ply_open(filename.c_str(), nullptr, 0, nullptr); + if (!ply) { + // std::cout << "Open PLY file error!" << std::endl; + return false; + } + + // read file header + if (!ply_read_header(ply)) { + ply_close(ply); + // std::cout << "Open PLY header error!" << std::endl; + return false; + } + + // get vertex number and face number + p_ply_element element = nullptr; + uint32_t nv = 0, nf = 0; + while ((element = ply_get_next_element(ply, element)) != nullptr) { + const char *name; + long nInstances; + ply_get_element_info(element, &name, &nInstances); + if (!strcmp(name, "vertex")) + nv = (uint32_t)nInstances; + else if (!strcmp(name, "face")) + nf = (uint32_t)nInstances; + } + + // init F&V + F.resize(3 * nf); + V.resize(3 * nv); + + // callback + auto rply_vertex_cb = [](p_ply_argument argument) -> int { + vector *pV; long index, coord; + ply_get_argument_user_data(argument, (void **)&pV, &coord); + ply_get_argument_element(argument, nullptr, &index); + (*pV)[3 * index + coord] = (float)ply_get_argument_value(argument); + return 1; + }; + + auto rply_index_cb = [](p_ply_argument argument) -> int { + vector *pF; + ply_get_argument_user_data(argument, (void **)&pF, nullptr); + long length, value_index, index; + ply_get_argument_property(argument, nullptr, &length, &value_index); + //if (length != 3) throw std::runtime_error("Only triangle faces are supported!"); + ply_get_argument_element(argument, nullptr, &index); + if (value_index >= 0) + (*pF)[3 * index + value_index] = (int)ply_get_argument_value(argument); + return 1; + }; + + // set vertex callback + ply_set_read_cb(ply, "vertex", "x", rply_vertex_cb, &V, 0); + ply_set_read_cb(ply, "vertex", "y", rply_vertex_cb, &V, 1); + ply_set_read_cb(ply, "vertex", "z", rply_vertex_cb, &V, 2); + + // set face callback + long nfr = ply_set_read_cb(ply, "face", "vertex_indices", rply_index_cb, &F, 0); + if (nfr < 1) ply_set_read_cb(ply, "face", "vertex_index", rply_index_cb, &F, 0); + + //ply_read + ply_read(ply); + ply_close(ply); + + return true; +} + +bool write_ply(const string& filename, const vector& V, const vector& F) { + // open ply + p_ply ply = ply_create(filename.c_str(), PLY_LITTLE_ENDIAN, nullptr, 0, nullptr); + if (!ply) { + // throw std::runtime_error("Unable to write PLY file!"); + return false; + } + + // add vertex + int nv = V.size() / 3; + ply_add_element(ply, "vertex", nv); + ply_add_scalar_property(ply, "x", PLY_FLOAT); + ply_add_scalar_property(ply, "y", PLY_FLOAT); + ply_add_scalar_property(ply, "z", PLY_FLOAT); + + // add face + int nf = F.size() / 3; + ply_add_element(ply, "face", nf); + ply_add_list_property(ply, "vertex_indices", PLY_UINT8, PLY_INT); + + // write header + ply_write_header(ply); + + // write vertex + for (int i = 0; i < nv; i++) { + for (int j = 0; j < 3; ++j) { + ply_write(ply, V[i * 3 + j]); + } + } + + // write face + for (int i = 0; i < nf; i++) { + ply_write(ply, 3); + for (int j = 0; j < 3; ++j) { + ply_write(ply, F[i * 3 + j]); + } + } + + // close ply + ply_close(ply); + return true; +} + +#else +#include +#include + +bool read_ply(const string& filename, vector& V, vector& F) { + std::ifstream infile(filename, std::ios::binary); + if (!infile) { + std::cerr << "Error, cannot read ply files!" << std::endl; + return false; + } + + happly::PLYData plyIn(infile); + V = plyIn.getVertices(); + F = plyIn.getTriFaces(); + + return true; +} + +bool write_ply(const string& filename, const vector& V, const vector& F) { + std::ofstream outfile(filename, std::ios::binary); + if (!outfile) { + std::cerr << "Error, cannot read ply files!" << std::endl; + return false; + } + + happly::PLYData plyOut; + plyOut.addVertices(V); + plyOut.addTriFaces(F); + + plyOut.write(outfile); + return false; +} + +#endif + + +void compute_face_center(vector& Fc, const vector& V, + const vector& F) { + int nf = F.size() / 3; + Fc.assign(3 * nf, 0); + #pragma omp parallel for + for (int i = 0; i < nf; i++) { + int ix3 = i * 3; + for (int j = 0; j < 3; j++) { + int fx3 = F[ix3 + j] * 3; + for (int k = 0; k < 3; ++k) { + Fc[ix3 + k] += V[fx3 + k]; + } + } + for (int k = 0; k < 3; ++k) { + Fc[ix3 + k] /= 3.0f; + } + } +} + +void compute_face_normal(vector& face_normal, vector& face_area, + const vector& V, const vector& F) { + int nf = F.size() / 3; + face_normal.resize(3 * nf); + face_area.resize(nf); + const float* pt = V.data(); + const float EPS = 1.0e-10; + float* normal = face_normal.data(); + + #pragma omp parallel for + for (int i = 0; i < nf; i++) { + int ix3 = i * 3; + const float* v0 = pt + F[ix3] * 3; + const float* v1 = pt + F[ix3 + 1] * 3; + const float* v2 = pt + F[ix3 + 2] * 3; + + float p01[3], p02[3]; + for (int j = 0; j < 3; ++j) { + p01[j] = v1[j] - v0[j]; + p02[j] = v2[j] - v0[j]; + } + + float* normal_i = normal + ix3; + cross_prod(normal_i, p01, p02); + float len = norm2(normal_i, 3); + if (len < EPS) len = EPS; + for (int j = 0; j < 3; ++j) { + normal_i[j] /= len; + } + + face_area[i] = len * 0.5; + } +} \ No newline at end of file diff --git a/octree/octree/mesh.h b/octree/octree/mesh.h new file mode 100644 index 0000000..7ac9439 --- /dev/null +++ b/octree/octree/mesh.h @@ -0,0 +1,25 @@ +#ifndef _OCTREE_MESH_ +#define _OCTREE_MESH_ + +#include +#include + +using std::vector; +using std::string; + +// I/O +bool read_mesh(const string& filename, vector& V, vector& F); +bool write_mesh(const string& filename, const vector& V, const vector& F); + +bool read_obj(const string& filename, vector& V, vector& F); +bool write_obj(const string& filename, const vector& V, const vector& F); +bool read_off(const string& filename, vector& V, vector& F); +bool read_ply(const string& filename, vector& V, vector& F); +bool write_ply(const string& filename, const vector& V, const vector& F); + +// mesh related calculation +void compute_face_center(vector& Fc, const vector& V, const vector& F); +void compute_face_normal(vector& face_normal, vector& face_area, + const vector& V, const vector& F); + +#endif // _OCTREE_MESH_ diff --git a/octree/octree/octree.cpp b/octree/octree/octree.cpp new file mode 100644 index 0000000..c516c28 --- /dev/null +++ b/octree/octree/octree.cpp @@ -0,0 +1,1408 @@ +#include "octree.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "math_functions.h" +#include "octree_nn.h" +#include "marching_cube.h" + + +void Octree::set_octree(const Octree& octree_in) { + buffer_ = octree_in.buffer(); + this->set_cpu(buffer_.data()); +} + +void Octree::set_octree(vector& data) { + buffer_.swap(data); + this->set_cpu(buffer_.data()); +} + +void Octree::set_octree(const char* data, const int sz) { + resize_octree(sz); + memcpy(buffer_.data(), data, sz); + this->set_cpu(buffer_.data()); +} + +void Octree::resize_octree(const int sz) { + buffer_.resize(sz); + this->set_cpu(buffer_.data()); +} + +bool Octree::read_octree(const string& filename) { + std::ifstream infile(filename, std::ios::binary); + if (!infile) return false; + + infile.seekg(0, infile.end); + size_t len = infile.tellg(); + infile.seekg(0, infile.beg); + if (len < sizeof(OctreeInfo)) { + // the file should at least contain a OctreeInfo structure + infile.close(); + return false; + } + std::cout << "size of octree info: " << sizeof(OctreeInfo) <set_cpu(buffer_.data()); + + infile.close(); + return true; +} + +bool Octree::write_octree(const string& filename) const { + std::ofstream outfile(filename, std::ios::binary); + if (!outfile) return false; + outfile.write(buffer_.data(), buffer_.size()); + outfile.close(); + return true; +} + +std::string Octree::get_binary_string() const { + return std::string(buffer_.cbegin(), buffer_.cend()); +} + +void Octree::build(const OctreeInfo& octree_info, const Points& point_cloud) { + // init + clear(octree_info.depth()); + oct_info_ = octree_info; + info_ = &oct_info_; + + // preprocess, get key and sort + vector pts_scaled; + normalize_pts(pts_scaled, point_cloud); + vector node_keys, sorted_idx; + sort_keys(node_keys, sorted_idx, pts_scaled); + vector unique_idx; + unique_key(node_keys, unique_idx); + + // build octree structure + build_structure(node_keys); + + // set nnum_[], nnum_cum_[], nnum_nempty_[] and ptr_dis_[] + calc_node_num(); + + // average the signal for the last octree layer + calc_signal(point_cloud, pts_scaled, sorted_idx, unique_idx); + + // average the signal for the other octree layers + if (oct_info_.locations(OctreeInfo::kFeature) == -1) { + covered_depth_nodes(); + + bool has_normal = point_cloud.info().has_property(PointsInfo::kNormal); + bool calc_norm_err = oct_info_.is_adaptive() && has_normal; + bool calc_dist_err = oct_info_.is_adaptive() && oct_info_.has_displace() && has_normal; + calc_signal(calc_norm_err, calc_dist_err); + } + + // generate split label + if (oct_info_.has_property(OctreeInfo::kSplit)) { + calc_split_label(); + } + + // extrapolate node feature + if (oct_info_.extrapolate() && oct_info_.locations(OctreeInfo::kFeature) == -1) { + extrapolate_signal(); + } + + // serialization + serialize(); + + trim_octree(); +} + +void Octree::clear(int depth) { + keys_.clear(); + children_.clear(); + displacement_.clear(); + split_labels_.clear(); + avg_normals_.clear(); + avg_features_.clear(); + avg_pts_.clear(); + avg_labels_.clear(); + max_label_ = 0; + buffer_.clear(); + info_ = nullptr; + dnum_.clear(); + didx_.clear(); + normal_err_.clear(); + distance_err_.clear(); + + if (depth == 0) return; + keys_.resize(depth + 1); + children_.resize(depth + 1); + displacement_.resize(depth + 1); + split_labels_.resize(depth + 1); + avg_normals_.resize(depth + 1); + avg_features_.resize(depth + 1); + avg_pts_.resize(depth + 1); + avg_labels_.resize(depth + 1); + dnum_.resize(depth + 1); + didx_.resize(depth + 1); + normal_err_.resize(depth + 1); + distance_err_.resize(depth + 1); +} + +void Octree::normalize_pts(vector& pts_scaled, const Points& point_cloud) { + const float* bbmin = oct_info_.bbmin(); + const float* pts = point_cloud.ptr(PointsInfo::kPoint); + const int npt = point_cloud.info().pt_num(); + const float mul = float(1 << oct_info_.depth()) / oct_info_.bbox_max_width(); + pts_scaled.resize(3 * npt); + + // normalize the points into the range [0, 1 << depth_) using bbox_width + #pragma omp parallel for + for (int i = 0; i < npt; i++) { + int i3 = i * 3; + for (int j = 0; j < 3; j++) { + pts_scaled[i3 + j] = (pts[i3 + j] - bbmin[j]) * mul; + } + } +} + +void Octree::sort_keys(vector& sorted_keys, vector& sorted_idx, + const vector& pts_scaled) { + + // compute the code + int depth_ = oct_info_.depth(); + int npt = pts_scaled.size() / 3; + vector code(npt); + #pragma omp parallel for + for (int i = 0; i < npt; i++) { + // compute key + uint32 pt[3], key; + for (int j = 0; j < 3; ++j) { + pt[j] = static_cast(pts_scaled[3 * i + j]); + } + compute_key(key, pt, depth_); + + // generate code + uint32* ptr = reinterpret_cast(&code[i]); + ptr[0] = i; + ptr[1] = key; + } + + // sort all the code + std::sort(code.begin(), code.end()); + + // unpack the code + sorted_keys.resize(npt); + sorted_idx.resize(npt); + #pragma omp parallel for + for (int i = 0; i < npt; i++) { + uint32* ptr = reinterpret_cast(&code[i]); + sorted_idx[i] = ptr[0]; + sorted_keys[i] = ptr[1]; + } +} + +void Octree::build_structure(vector& node_keys) { + const int depth_ = oct_info_.depth(); + const int full_layer_ = oct_info_.full_layer(); + children_.resize(depth_ + 1); + keys_.resize(depth_ + 1); + + // layer 0 to full_layer_: the octree is full in these layers + for (int curr_depth = 0; curr_depth <= full_layer_; curr_depth++) { + vector& children = children_[curr_depth]; + vector& keys = keys_[curr_depth]; + + int n = 1 << 3 * curr_depth; + keys.resize(n, -1); children.resize(n, -1); + for (int i = 0; i < n; i++) { + keys[i] = i; + if (curr_depth != full_layer_) { + children[i] = i; + } + } + } + + // layer depth_ to full_layer_ + for (int curr_depth = depth_; curr_depth > full_layer_; --curr_depth) { + // compute parent key, i.e. keys of layer (curr_depth -1) + int n = node_keys.size(); + vector parent_keys(n); + #pragma omp parallel for + for (int i = 0; i < n; i++) { + parent_keys[i] = node_keys[i] >> 3; + } + + // compute unique parent key + vector parent_pidx; + unique_key(parent_keys, parent_pidx); + + // augment children keys and create nodes + int np = parent_keys.size(); + int nch = np << 3; + vector& children = children_[curr_depth]; + vector& keys = keys_[curr_depth]; + children.resize(nch, -1); + keys.resize(nch, 0); + + for (int i = 0; i < nch; i++) { + int j = i >> 3; + keys[i] = (parent_keys[j] << 3) | (i % 8); + } + + // compute base address for each node + vector addr(nch); + for (int i = 0; i < np; i++) { + for (uint32 j = parent_pidx[i]; j < parent_pidx[i + 1]; j++) { + addr[j] = i << 3; + } + } + + // set children pointer and parent pointer + #pragma omp parallel for + for (int i = 0; i < n; i++) { + // address + uint32 k = (node_keys[i] & 7u) | addr[i]; + + // set children pointer for layer curr_depth + children[k] = i; + } + + // save data and prepare for the following iteration + node_keys.swap(parent_keys); + } + + // set the children for the layer full_layer_ + // Now the node_keys are the key for full_layer + if (depth_ > full_layer_) { + for (int i = 0; i < node_keys.size(); i++) { + uint32 j = node_keys[i]; + children_[full_layer_][j] = i; + } + } +} + +void Octree::calc_node_num() { + const int depth = oct_info_.depth(); + + vector node_num(depth + 1, 0); + for (int d = 0; d <= depth; ++d) { + node_num[d] = keys_[d].size(); + } + + vector node_num_nempty(depth + 1, 0); + for (int d = 0; d <= depth; ++d) { + // find the last element which is not equal to -1 + const vector& children_d = children_[d]; + for (int i = node_num[d] - 1; i >= 0; i--) { + if (children_d[i] != -1) { + node_num_nempty[d] = children_d[i] + 1; + break; + } + } + } + + oct_info_.set_nnum(node_num.data()); + oct_info_.set_nempty(node_num_nempty.data()); + oct_info_.set_nnum_cum(); + oct_info_.set_ptr_dis(); // !!! note: call this function to update the ptr +} + +// compute the average signal for the last octree layer +void Octree::calc_signal(const Points& point_cloud, const vector& pts_scaled, + const vector& sorted_idx, const vector& unique_idx) { + int depth = oct_info_.depth(); + const float* normals = point_cloud.ptr(PointsInfo::kNormal); + const float* features = point_cloud.ptr(PointsInfo::kFeature); + const float* labels = point_cloud.ptr(PointsInfo::kLabel); + const int nnum = oct_info_.node_num(depth); + + const vector& children = children_[depth]; + if (normals != nullptr) { + const int channel = point_cloud.info().channel(PointsInfo::kNormal); + avg_normals_[depth].assign(channel * nnum, 0.0f); + + #pragma omp parallel for + for (int i = 0; i < nnum; i++) { + int t = children[i]; + if (node_type(t) == kLeaf) continue; + + vector avg_normal(channel, 0.0f); + for (uint32 j = unique_idx[t]; j < unique_idx[t + 1]; j++) { + int h = sorted_idx[j]; + for (int c = 0; c < channel; ++c) { + avg_normal[c] += normals[channel * h + c]; + } + } + + float factor = norm2(avg_normal); + if (factor < 1.0e-6f) { + int h = sorted_idx[unique_idx[t]]; + for (int c = 0; c < channel; ++c) { + avg_normal[c] = normals[channel * h + c]; + } + factor = norm2(avg_normal) + ESP; + } + for (int c = 0; c < channel; ++c) { + avg_normals_[depth][c * nnum + i] = avg_normal[c] / factor; + } + } + } + + if (features != nullptr) { + const int channel = point_cloud.info().channel(PointsInfo::kFeature); + avg_features_[depth].assign(channel * nnum, 0.0f); + + #pragma omp parallel for + for (int i = 0; i < nnum; i++) { + int t = children[i]; + if (node_type(t) == kLeaf) continue; + + vector avg_feature(channel, 0.0f); + for (uint32 j = unique_idx[t]; j < unique_idx[t + 1]; j++) { + int h = sorted_idx[j]; + for (int c = 0; c < channel; ++c) { + avg_feature[c] += features[channel * h + c]; + } + } + + float factor = unique_idx[t + 1] - unique_idx[t] + ESP; + for (int c = 0; c < channel; ++c) { + avg_features_[depth][c * nnum + i] = avg_feature[c] / factor; + } + } + } + + if (labels != nullptr) { + // the channel of label is fixed as 1 + avg_labels_[depth].assign(nnum, -1.0f); // initialize as -1 + const int npt = point_cloud.info().pt_num(); + max_label_ = static_cast(*std::max_element(labels, labels + npt)) + 1; + + #pragma omp parallel for + for (int i = 0; i < nnum; i++) { + int t = children[i]; + if (node_type(t) == kLeaf) continue; + + int valid_num = 0; + vector avg_label(max_label_, 0); + for (uint32 j = unique_idx[t]; j < unique_idx[t + 1]; j++) { + int h = sorted_idx[j]; + int l = static_cast(labels[h]); + if (l < 0) { continue; } // invalid labels + avg_label[l] += 1; + valid_num += 1; + } + if (valid_num > 0) { + avg_labels_[depth][i] = static_cast(std::distance(avg_label.begin(), + std::max_element(avg_label.begin(), avg_label.end()))); + } + } + } + + if (oct_info_.has_displace() || oct_info_.save_pts()) { + const int channel = 3; + const float mul = 1.1547f; // = 2.0f / sqrt(3.0f) + avg_pts_[depth].assign(nnum * channel, 0.0f); + int channel_dis = normals == nullptr ? 4 : 1; + vector& displacement = displacement_[depth]; + displacement.assign(channel_dis * nnum, 0.0f); + + #pragma omp parallel for + for (int i = 0; i < nnum; i++) { + int t = children[i]; + if (node_type(t) == kLeaf) continue; + + float avg_pt[3] = { 0.0f, 0.0f, 0.0f }; + for (uint32 j = unique_idx[t]; j < unique_idx[t + 1]; j++) { + int h = sorted_idx[j]; + for (int c = 0; c < 3; ++c) { + avg_pt[c] += pts_scaled[3 * h + c]; + } + } + + float dis[4] = {0.0f, 0.0f, 0.0f, 0.0f }; + float factor = unique_idx[t + 1] - unique_idx[t] + ESP; // points number + for (int c = 0; c < 3; ++c) { + avg_pt[c] /= factor; + + float fract_part = 0.0f, int_part = 0.0f; + fract_part = std::modf(avg_pt[c], &int_part); + + dis[c] = fract_part - 0.5f; + if (normals != nullptr) { + dis[3] += dis[c] * avg_normals_[depth][c * nnum + i]; + } else { + dis[3] = 1.0f; + } + + avg_pts_[depth][c * nnum + i] = avg_pt[c]; + } + + if (normals != nullptr) { + displacement[i] = dis[3] * mul; // !!! note the *mul* !!! + } else { + for (int c = 0; c < 3; ++c) { + displacement[c * nnum + i] = dis[c]; + } + displacement[3 * nnum + i] = dis[3] * mul; // !!! note the *mul* !!! + } + } + } +} + +void Octree::calc_signal(const bool calc_normal_err, const bool calc_dist_err) { + const int depth = oct_info_.depth(); + const int depth_adp = oct_info_.adaptive_layer(); + const int nnum_depth = oct_info_.node_num(depth); + const float imul = 2.0f / sqrtf(3.0f); + const vector& children_depth = children_[depth]; + const vector& normal_depth = avg_normals_[depth]; + const vector& pt_depth = avg_pts_[depth]; + const vector& feature_depth = avg_features_[depth]; + const vector& label_depth = avg_labels_[depth]; + + const int channel_pt = pt_depth.size() / nnum_depth; + const int channel_normal = normal_depth.size() / nnum_depth; + const int channel_feature = feature_depth.size() / nnum_depth; + const int channel_label = label_depth.size() / nnum_depth; + + const bool has_pt = !pt_depth.empty(); + const bool has_dis = !displacement_[depth].empty(); + const bool has_normal = !normal_depth.empty(); + const bool has_feature = !feature_depth.empty(); + const bool has_label = !label_depth.empty(); + + if (calc_normal_err) normal_err_[depth].resize(nnum_depth, 1.0e20f); + if (calc_dist_err) distance_err_[depth].resize(nnum_depth, 1.0e20f); + + for (int d = depth - 1; d >= 0; --d) { + const vector& dnum_d = dnum_[d]; + const vector& didx_d = didx_[d]; + const vector& children_d = children_[d]; + const vector& key_d = keys_[d]; + const float scale = static_cast(1 << (depth - d)); + + vector& normal_d = avg_normals_[d]; + vector& pt_d = avg_pts_[d]; + vector& label_d = avg_labels_[d]; + vector& feature_d = avg_features_[d]; + vector& displacement_d = displacement_[d]; + vector& normal_err_d = normal_err_[d]; + vector& distance_err_d = distance_err_[d]; + + const int nnum_d = oct_info_.node_num(d); + int channel_dis = has_normal ? 1 : 4; + if (has_normal) normal_d.assign(nnum_d * channel_normal, 0.0f); + if (has_pt) pt_d.assign(nnum_d * channel_pt, 0.0f); + if (has_feature) feature_d.assign(nnum_d * channel_feature, 0.0f); + if (has_label) label_d.assign(nnum_d * channel_label, -1.0f);// !!! init as -1 + if (has_dis) displacement_d.assign(channel_dis * nnum_d, 0.0f); + if (calc_normal_err) normal_err_d.assign(nnum_d, 1.0e20f); // !!! initialized + if (calc_dist_err) distance_err_d.assign(nnum_d, 1.0e20f); // !!! as 1.0e20f + + for (int i = 0; i < nnum_d; ++i) { + if (node_type(children_d[i]) == kLeaf) continue; + + vector n_avg(channel_normal, 0.0f); + if (has_normal) { + for (int j = didx_d[i]; j < didx_d[i] + dnum_d[i]; ++j) { + if (node_type(children_depth[j]) == kLeaf) continue; + for (int c = 0; c < channel_normal; ++c) { + n_avg[c] += normal_depth[c * nnum_depth + j]; + } + } + + float len = norm2(n_avg); + if (len < 1.0e-6f) { + for (int j = didx_d[i]; j < didx_d[i] + dnum_d[i]; ++j) { + if (node_type(children_depth[j]) == kLeaf) continue; + for (int c = 0; c < channel_normal; ++c) { + n_avg[c] = normal_depth[c * nnum_depth + j]; + } + } + len = norm2(n_avg) + ESP; + } + + for (int c = 0; c < channel_normal; ++c) { + n_avg[c] /= len; + normal_d[c * nnum_d + i] = n_avg[c]; // output + } + } + + float count = ESP; // the non-empty leaf node in the finest layer + for (int j = didx_d[i]; j < didx_d[i] + dnum_d[i]; ++j) { + if (node_type(children_depth[j]) != kLeaf) count += 1.0f; + } + + vector pt_avg(channel_pt, 0.0f); + if (has_pt) { + for (int j = didx_d[i]; j < didx_d[i] + dnum_d[i]; ++j) { + if (node_type(children_depth[j]) == kLeaf) continue; + for (int c = 0; c < channel_pt; ++c) { + pt_avg[c] += pt_depth[c * nnum_depth + j]; + } + } + + for (int c = 0; c < channel_pt; ++c) { + pt_avg[c] /= count * scale; // !!! note the scale + pt_d[c * nnum_d + i] = pt_avg[c]; // output + } + } + + vector f_avg(channel_feature, 0.0f); + if (has_feature) { + for (int j = didx_d[i]; j < didx_d[i] + dnum_d[i]; ++j) { + if (node_type(children_depth[j]) == kLeaf) continue; + for (int c = 0; c < channel_feature; ++c) { + f_avg[c] += feature_depth[c * nnum_depth + j]; + } + } + + for (int c = 0; c < channel_feature; ++c) { + f_avg[c] /= count; + feature_d[c * nnum_d + i] = f_avg[c]; // output + } + } + + vector l_avg(max_label_, 0); + if (has_label) { + int valid_num = 0; + for (int j = didx_d[i]; j < didx_d[i] + dnum_d[i]; ++j) { + int l = static_cast(label_depth[j]); + if (l < 0) continue; + l_avg[l] += 1; + valid_num += 1; + } + if (valid_num > 0) { + label_d[i] = static_cast(std::distance(l_avg.begin(), + std::max_element(l_avg.begin(), l_avg.end()))); + } + } + + uint32 ptu_base[3]; + compute_pt(ptu_base, key_d[i], d); + float pt_base[3] = { ptu_base[0], ptu_base[1], ptu_base[2] }; + if (has_dis) { + float dis_avg[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + for (int c = 0; c < 3; ++c) { + float fract_part = pt_avg[c] - pt_base[c]; + dis_avg[c] = fract_part - 0.5f; + if (has_normal) { + dis_avg[3] += dis_avg[c] * n_avg[c]; + } else { + dis_avg[3] = 1.0f; + } + } + if (!has_normal) { + for (int c = 0; c < 3; ++c) { + displacement_d[c * nnum_d + i] = dis_avg[c]; + } + displacement_d[3 * nnum_d + i] = dis_avg[3] * imul; + } else { + displacement_d[i] = dis_avg[3] * imul; // IMPORTANT: RESCALE + } + } + + float nm_err = 0.0f; + if (calc_normal_err && has_normal && d >= depth_adp) { + for (int j = didx_d[i]; j < didx_d[i] + dnum_d[i]; ++j) { + if (node_type(children_depth[j]) == kLeaf) continue; + for (int c = 0; c < 3; ++c) { + float tmp = normal_depth[c * nnum_depth + j] - n_avg[c]; + nm_err += tmp * tmp; + } + } + nm_err /= count; + normal_err_d[i] = nm_err; + } + + if (calc_dist_err && has_pt && d >= depth_adp) { + // the error from the original geometry to the averaged geometry + float distance_max1 = -1.0f; + // !!! note the scale + float pt_avg1[3] = { pt_avg[0] * scale, pt_avg[1] * scale, pt_avg[2] * scale }; + for (int j = didx_d[i]; j < didx_d[i] + dnum_d[i]; ++j) { + if (node_type(children_depth[j]) == kLeaf) continue; + + float dis = 0.0f; + for (int c = 0; c < 3; ++c) { + dis += (pt_depth[c * nnum_depth + j] - pt_avg1[c]) * n_avg[c]; + } + dis = abs(dis); + if (dis > distance_max1) distance_max1 = dis; + } + + // the error from the averaged geometry to the original geometry + float distance_max2 = -1; + vector vtx; + intersect_cube(vtx, pt_avg.data(), pt_base, n_avg.data()); + if (vtx.empty()) distance_max2 = 5.0e10f; // !!! the degenerated case, ||n_avg|| == 0 + for (auto& v : vtx) v *= scale; // !!! note the scale + for (int k = 0; k < vtx.size() / 3; ++k) { + // min + float distance_min = 1.0e30f; + for (int j = didx_d[i]; j < didx_d[i] + dnum_d[i]; ++j) { + if (node_type(children_depth[j]) == kLeaf) continue; + float dis = 0.0f; + for (int c = 0; c < 3; ++c) { + float ptc = pt_depth[c * nnum_depth + j] - vtx[3 * k + c]; + dis += ptc * ptc; + } + dis = sqrtf(dis); + if (dis < distance_min) distance_min = dis; + } + + // max + if (distance_min > distance_max2) distance_max2 = distance_min; + } + + distance_err_d[i] = std::max(distance_max2, distance_max1); + } + } + } +} + +void Octree::extrapolate_signal() { + const int depth = oct_info_.depth(); + const int full_layer = oct_info_.full_layer(); + const int nnum_depth = oct_info_.node_num(depth); + const float imul = 2.0f / sqrtf(3.0f); + + const int channel_normal = avg_normals_[depth].size() / nnum_depth; + const int channel_feature = avg_features_[depth].size() / nnum_depth; + const int channel_label = avg_labels_[depth].size() / nnum_depth; + + const bool has_dis = !displacement_[depth].empty(); + const bool has_normal = !avg_normals_[depth].empty(); + const bool has_feature = !avg_features_[depth].empty(); + const bool has_label = !avg_labels_[depth].empty(); + + for (int d = depth; d >= full_layer; --d) { + const int nnum_d = oct_info_.node_num(d); + const vector& children_d = children_[d]; + vector& normal_d = avg_normals_[d]; + vector& label_d = avg_labels_[d]; + vector& feature_d = avg_features_[d]; + vector& displacement_d = displacement_[d]; + + for (int i = 0; i < nnum_d; ++i) { + if (node_type(children_d[i]) != kLeaf) continue; + int id = i % 8; + int i_base = i - id; + + float count = ESP; // the non-empty node number + for (int j = i_base; j < i_base + 8; ++j) { + if (node_type(children_d[j]) != kLeaf) count += 1.0f; + } + + vector n_avg(channel_normal, 0.0f); + if (has_normal) { + for (int j = i_base; j < i_base + 8; ++j) { + if (node_type(children_d[j]) == kLeaf) continue; + for (int c = 0; c < channel_normal; ++c) { + n_avg[c] += normal_d[c * nnum_d + j]; + } + } + + float ilen = 1.0f / (norm2(n_avg) + ESP); + for (int c = 0; c < channel_normal; ++c) { + n_avg[c] *= ilen; + normal_d[c * nnum_d + i] = n_avg[c]; // output + } + } + + vector f_avg(channel_feature, 0.0f); + if (has_feature) { + for (int j = i_base; j < i_base + 8; ++j) { + if (node_type(children_d[j]) == kLeaf) continue; + for (int c = 0; c < channel_feature; ++c) { + f_avg[c] += feature_d[c * nnum_d + j]; + } + } + + for (int c = 0; c < channel_feature; ++c) { + f_avg[c] /= count; + feature_d[c * nnum_d + i] = f_avg[c]; // output + } + } + + vector l_avg(max_label_, 0); + if (has_label) { + int valid_num = 0; + for (int j = i_base; j < i_base + 8; ++j) { + int l = static_cast(label_d[j]); + if (l < 0) { continue; } // invalid labels + l_avg[l] += 1; + valid_num += 1; + } + if (valid_num > 0) { + label_d[i] = static_cast(std::distance(l_avg.begin(), + std::max_element(l_avg.begin(), l_avg.end()))); + } + } + + if (has_dis && count > 0.5f) { + float xyzs[8][3] = { + {0, 0, 0}, {0, 0, 1.0f}, {0, 1.0f, 0}, {0, 1.0f, 1.0f}, + {1.0f, 0, 0}, {1.0f, 0, 1.0f}, {1.0f, 1.0f, 0}, {1.0f, 1.0f, 1.0f}, + }; + float dis = 0; + for (int j = i_base; j < i_base + 8; ++j) { + if (node_type(children_d[j]) == kLeaf) continue; + dis += displacement_d[j]; + for (int c = 0; c < channel_normal; ++c) { + dis += normal_d[c * nnum_d + j] * (xyzs[j % 8][c] - xyzs[id][c]) * imul; + } + } + dis /= count; + if (dis > 3.0f) dis = 3.0f; + if (dis < -3.0f) dis = -3.0f; + if (abs(dis) < 1.0f) { + //bool has_intersection = false; + // make the voxel has no intersection with the current voxel + unsigned int cube_cases = 0; + for (int k = 0; k < 8; ++k) { + float fval = dis; + for (int j = 0; j < 3; ++j) { + fval += (0.5f - MarchingCube::corner_[k][j]) * n_avg[j] * imul; + } + if (fval < 0) cube_cases |= (1 << k); + } + if (cube_cases != 255 && cube_cases != 0) { + dis = dis < 0 ? -1.0f : 1.0f; + } + } + + displacement_d[i] = dis; + } + + if (has_dis && count < 0.5f) { + // find the closest point + int j_min = -1; + float pti[3], ptj[3], dis_min = 1.0e30f; + key2xyz(pti, keys_[d][i], d); + for (int j = 0; j < nnum_d; ++j) { + if (node_type(children_d[j]) == kLeaf) continue; + key2xyz(ptj, keys_[d][j], d); + float dd = abs(pti[0] - ptj[0]) + abs(pti[1] - ptj[1]) + abs(pti[2] - ptj[2]); + if (dd < dis_min) { + dis_min = dd; + j_min = j; + } + } + // calc the displacement + float dis = displacement_d[j_min]; + key2xyz(ptj, keys_[d][j_min], d); + for (int c = 0; c < channel_normal; ++c) { + dis += normal_d[c * nnum_d + j_min] * (ptj[c] - pti[c]) * imul; + } + if (dis > 0.0f) dis = 2.0f; + if (dis < 0.0f) dis = -2.0f; + displacement_d[i] = dis; + } + } + } +} + +bool Octree::save_legacy(std::string& filename) { + int depth_ = oct_info_.depth(); + int full_layer_ = oct_info_.full_layer(); + + std::ofstream outfile(filename, std::ios::binary); + if (!outfile) return false; + + vector node_num; + for (auto& keys : keys_) { + node_num.push_back(keys.size()); + } + + vector node_num_accu(depth_ + 2, 0); + for (int i = 1; i < depth_ + 2; ++i) { + node_num_accu[i] = node_num_accu[i - 1] + node_num[i - 1]; + } + int total_node_num = node_num_accu[depth_ + 1]; + int final_node_num = node_num[depth_]; + + // calc key + std::vector key(total_node_num), children(total_node_num); + int idx = 0; + for (int d = 0; d <= depth_; ++d) { + vector& keys = keys_[d]; + for (int i = 0; i < keys.size(); ++i) { + // calc point + uint32 k = keys[i], pt[3]; + compute_pt(pt, k, d); + + // compress + unsigned char* ptr = reinterpret_cast(&key[idx]); + ptr[0] = static_cast(pt[0]); + ptr[1] = static_cast(pt[1]); + ptr[2] = static_cast(pt[2]); + ptr[3] = static_cast(d); + + // children + children[idx] = children_[d][i]; + + // update index + idx++; + } + } + + // write + outfile.write((char*)&total_node_num, sizeof(int)); + outfile.write((char*)&final_node_num, sizeof(int)); + outfile.write((char*)&depth_, sizeof(int)); + outfile.write((char*)&full_layer_, sizeof(int)); + outfile.write((char*)node_num.data(), sizeof(int) * (depth_ + 1)); + outfile.write((char*)node_num_accu.data(), sizeof(int) * (depth_ + 2)); + outfile.write((char*)key.data(), sizeof(int) * total_node_num); + outfile.write((char*)children.data(), sizeof(int) * total_node_num); + outfile.write((char*)avg_normals_[depth_].data(), sizeof(float)*avg_normals_[depth_].size()); + outfile.write((char*)displacement_[depth_].data(), sizeof(float)*displacement_[depth_].size()); + outfile.write((char*)avg_labels_[depth_].data(), sizeof(float)*avg_labels_[depth_].size()); + outfile.close(); + + return true; +} + +//void Octree::set_bbox(const float* bbmin, const float* bbmax) { +// float center[3]; +// bbox_width_ = -1.0e20f; +// for (int i = 0; i < 3; ++i) { +// float dis = bbmax[i] - bbmin[i]; +// if (dis > bbox_width_) bbox_width_ = dis; +// center[i] = (bbmin[i] + bbmax[i]) * 0.5f; +// } +// +// // deal with degenarated case +// if (bbox_width_ == 0.0) bbox_width_ = ESP; +// +// // set the bounding box and place the object in the center +// float radius = bbox_width_ * 0.5f; +// for (int i = 0; i < 3; ++i) { +// bbmax_[i] = center[i] + radius; +// bbmin_[i] = center[i] - radius; +// } +//} + +void Octree::unique_key(vector& keys, vector& idx) { + idx.clear(); + idx.push_back(0); + + int n = keys.size(), j = 1; + for (int i = 1; i < n; i++) { + if (keys[i] != keys[i - 1]) { + idx.push_back(i); + keys[j++] = keys[i]; + } + } + + keys.resize(j); + idx.push_back(n); +} + + +void Octree::serialize() { + const int sz = oct_info_.sizeof_octree(); + buffer_.resize(sz, 0); + this->set_cpu(buffer_.data(), &oct_info_); + //info_ = reinterpret_cast(buffer_.data()); + //*info_ = oct_info_; + + // concatenate the avg_normals_, avg_features_, and displacement_ into features + const int depth = oct_info_.depth(); + vector > features = avg_normals_; + for (int d = 0; d <= depth; ++d) { + if (oct_info_.has_displace()) { + features[d].insert(features[d].end(), displacement_[d].begin(), displacement_[d].end()); + } + // if there is no features in points, the avg_features_ will also be empty + features[d].insert(features[d].end(), avg_features_[d].begin(), avg_features_[d].end()); + if (oct_info_.save_pts()) { + features[d].insert(features[d].end(), avg_pts_[d].begin(), avg_pts_[d].end()); + } + } + +#define SERIALIZE_PROPERTY(Dtype, Ptype, Var) \ + do { if (oct_info_.has_property(Ptype)) { \ + Dtype* ptr = reinterpret_cast(mutable_ptr_cpu(Ptype, 0)); \ + serialize(ptr, Var, oct_info_.locations(Ptype)); } \ + } while(0) \ + + if (oct_info_.is_key2xyz()) { + vector > xyz; + key_to_xyz(xyz); + SERIALIZE_PROPERTY(uint32, OctreeInfo::kKey, xyz); + } else { + SERIALIZE_PROPERTY(uint32, OctreeInfo::kKey, keys_); + } + SERIALIZE_PROPERTY(int, OctreeInfo::kChild, children_); + SERIALIZE_PROPERTY(float, OctreeInfo::kFeature, features); + SERIALIZE_PROPERTY(float, OctreeInfo::kLabel, avg_labels_); + SERIALIZE_PROPERTY(float, OctreeInfo::kSplit, split_labels_); +} + +template +void Octree::serialize(Dtype* des, const vector >& src, const int location) { + if (location == -1) { + for (int d = 0; d <= oct_info_.depth(); ++d) { + des = std::copy(src[d].begin(), src[d].end(), des); + } + } else { + std::copy(src[location].begin(), src[location].end(), des); + } +} + +void Octree::covered_depth_nodes() { + // init + const int depth_ = oct_info_.depth(); + for (int d = 0; d <= depth_; ++d) { + int nnum = oct_info_.node_num(d); + dnum_[d].assign(nnum, 0); + didx_[d].assign(nnum, -1); + } + + //layer-depth_ + int nnum = oct_info_.node_num(depth_); + for (int i = 0; i < nnum; ++i) { + dnum_[depth_][i] = 1; + didx_[depth_][i] = i; + } + + // layer-(depth_-1) + nnum = oct_info_.node_num(depth_ - 1); + for (int i = 0; i < nnum; ++i) { + int t = children_[depth_ - 1][i]; + if (node_type(t) == kLeaf) continue; + dnum_[depth_ - 1][i] = 8; + didx_[depth_ - 1][i] = t * 8; + } + + // layer-(depth-2) to layer-0 + for (int d = depth_ - 2; d >= 0; --d) { + nnum = oct_info_.node_num(d); + const vector children_d = children_[d]; + for (int i = 0; i < nnum; ++i) { + int t = children_d[i]; + if (node_type(t) == kLeaf) continue; + t *= 8; + for (int j = 0; j < 8; ++j) { + dnum_[d][i] += dnum_[d + 1][t + j]; + } + for (int j = 0; j < 8; ++j) { + if (didx_[d + 1][t + j] != -1) { + didx_[d][i] = didx_[d + 1][t + j]; + break; + } + } + } + } +} + +void Octree::trim_octree() { + if (!oct_info_.is_adaptive()) return; + const int depth = oct_info_.depth(); + const int depth_adp = oct_info_.adaptive_layer(); + const float th_dist = oct_info_.threshold_distance(); + const float th_norm = oct_info_.threshold_normal(); + const bool has_dis = oct_info_.has_displace(); + + // generate the drop flag + enum TrimType { kDrop = 0, kDropChildren = 1, kKeep = 2 }; + vector > drop(depth + 1); + for (int d = 0; d <= depth; ++d) { + drop[d].resize(oct_info_.node_num(d), kKeep); + } + for (int d = depth_adp; d <= depth; ++d) { + int nnum_dp = oct_info_.node_num(d - 1); + const vector& children_d = children_[d]; + const vector& children_dp = children_[d - 1]; + vector& drop_d = drop[d]; + vector& drop_dp = drop[d - 1]; + + bool all_drop = true; + // generate the drop flag + for (int i = 0; i < nnum_dp; ++i) { + int t = children_dp[i]; + if (node_type(t) == kLeaf) continue; + + // generate the drop flag for 8 children nodes: + // drop the node if its parent node is kDrop or kDropChildren, + // set the node as kDropChildren if the error is smaller than a threshold + for (int j = 0; j < 8; ++j) { + int idx = t * 8 + j; + if (drop_dp[i] == kKeep) { + // note that for all the leaf nodes and the finest nodes, + // distance_err_[d][i] is equal to 1.0e20f, so if it enters the following + // "if" body, the node_type(children_d[idx]) must be kInternelNode + //if (distance_err_[d][idx] < th_dist) { + if ((!has_dis || (has_dis && distance_err_[d][idx] < th_dist)) && + normal_err_[d][idx] < th_norm) { + drop_d[idx] = kDropChildren; + } + } else { + drop_d[idx] = kDrop; + } + + if (all_drop) { + // all_drop is false: there is at least one internal node which is kept + all_drop = !(drop_d[idx] == kKeep && + node_type(children_d[idx]) == kInternelNode); + } + } + } + + // make sure that there is at least one octree node in each layer + if (all_drop) { + int max_idx = 0; + float max_err = -1.0f; + for (int i = 0; i < nnum_dp; ++i) { + int t = children_dp[i]; + if (node_type(t) == kLeaf || drop_dp[i] != kKeep) continue; + + for (int j = 0; j < 8; ++j) { + int idx = t * 8 + j; + if (node_type(children_d[idx]) == kInternelNode && + normal_err_[d][idx] > max_err) { + max_err = normal_err_[d][idx]; + max_idx = idx; + } + } + } + drop_d[max_idx] = kKeep; + } + } + + // trim the octree + for (int d = depth_adp; d <= depth; ++d) { + int nnum_d = oct_info_.node_num(d); + const vector& drop_d = drop[d]; + + vector key; + for (int i = 0; i < nnum_d; ++i) { + if (drop_d[i] == kDrop) continue; + key.push_back(keys_[d][i]); + } + keys_[d].swap(key); + + vector children; + for (int i = 0, id = 0; i < nnum_d; ++i) { + if (drop_d[i] == kDrop) continue; + int ch = (drop_d[i] == kKeep && node_type(children_[d][i]) != kLeaf) ? id++ : -1; + children.push_back(ch); + } + children_[d].swap(children); + + auto trim_data = [&](vector& signal) { + vector data; + int channel = signal.size() / nnum_d; + if (channel == 0) return; + for (int i = 0; i < nnum_d; ++i) { + if (drop_d[i] == kDrop) continue; + for (int c = 0; c < channel; ++c) { + data.push_back(signal[c * nnum_d + i]); + } + } + //signal.swap(data); + // transpose + int num = data.size() / channel; + signal.resize(data.size()); + for (int i = 0; i < num; ++i) { + for (int c = 0; c < channel; ++c) { + signal[c * num + i] = data[i * channel + c]; + } + } + }; + + trim_data(displacement_[d]); + trim_data(avg_normals_[d]); + trim_data(avg_features_[d]); + trim_data(avg_labels_[d]); + } + + // update the node number + calc_node_num(); + + // generate split label + if (oct_info_.has_property(OctreeInfo::kSplit)) { + calc_split_label(); + } + + // serialization + serialize(); +} + +void Octree::key_to_xyz(vector >& xyz) { + const int depth = oct_info_.depth(); + const int channel = oct_info_.channel(OctreeInfo::kKey); + xyz.resize(depth + 1); + for (int d = 0; d <= depth; ++d) { + int nnum = oct_info_.node_num(d); + xyz[d].resize(nnum * channel, 0); + uint32* xyz_d = xyz[d].data(); + for (int i = 0; i < nnum; ++i) { + uint32 pt[3] = { 0, 0, 0 }; + compute_pt(pt, keys_[d][i], d); + + if (channel == 1) { + unsigned char* ptr = reinterpret_cast(xyz_d + i); + for (int c = 0; c < 3; ++c) { + ptr[c] = static_cast(pt[c]); + } + } else { + unsigned short* ptr = reinterpret_cast(xyz_d + 2 * i); + for (int c = 0; c < 3; ++c) { + ptr[c] = static_cast(pt[c]); + } + } + } + } +} + +void Octree::calc_split_label() { + const int depth = oct_info_.depth(); + const int channel = oct_info_.channel(OctreeInfo::kSplit); // is 1 (by default) + const bool adaptive = oct_info_.is_adaptive(); + + for (int d = 0; d <= depth; ++d) { + int nnum_d = oct_info_.node_num(d); + split_labels_[d].assign(nnum_d, 1); // initialize as 1 (non-empty, split) + for (int i = 0; i < nnum_d; ++i) { + if (node_type(children_[d][i]) == kLeaf) { + split_labels_[d][i] = 0; // empty node + if (adaptive) { + float t = abs(avg_normals_[d][i]) + abs(avg_normals_[d][nnum_d + i]) + + abs(avg_normals_[d][2 * nnum_d + i]); + // todo: t != 0 && has_intersection + if (t != 0) split_labels_[d][i] = 2; // surface-well-approximated + } + } + } + } +} + + +void Octree::valid_depth_range(int& depth_start, int& depth_end) const { + const int depth = info_->depth(); + const int depth_full = info_->full_layer(); + const int depth_adpt = info_->adaptive_layer(); + + depth_start = clamp(depth_start, depth_full, depth); + if (info_->is_adaptive() && depth_start < depth_adpt) depth_start = depth_adpt; + if (-1 != info_->locations(OctreeInfo::kFeature)) depth_start = depth; + + depth_end = clamp(depth_end, depth_start, depth); +} + + +void Octree::octree2pts(Points& point_cloud, int depth_start, int depth_end, + bool rescale) const { + const int depth = info_->depth(); + const float* bbmin = info_->bbmin(); + const float kMul = rescale ? info_->bbox_max_width() / float(1 << depth) : 1.0f; + valid_depth_range(depth_start, depth_end); + + vector pts, normals, labels; + for (int d = depth_start; d <= depth_end; ++d) { + const int* child_d = children_cpu(d); + const float* label_d = label_cpu(d); + const float scale = (1 << (depth - d)) * kMul; + const int num = info_->node_num(d); + + for (int i = 0; i < num; ++i) { + if ((node_type(child_d[i]) == kInternelNode && d != depth) || + (node_type(child_d[i]) == kLeaf && d == depth)) continue; + + float n[3], pt[3]; + node_normal(n, i, d); + float len = abs(n[0]) + abs(n[1]) + abs(n[2]); + if (len == 0 && d != depth) continue; // for adaptive octree + node_pos(pt, i, d); + + for (int c = 0; c < 3; ++c) { + normals.push_back(n[c]); + pts.push_back(pt[c] * scale + bbmin[c]); // !!! note the scale and bbmin + } + if (label_d != nullptr) labels.push_back(label_d[i]); + } + } + + point_cloud.set_points(pts, normals, vector(), labels); +} + +void Octree::octree2mesh(vector& V, vector& F, int depth_start, + int depth_end) const { + const int depth = info_->depth(); + const float* bbmin = info_->bbmin(); + const float kMul = info_->bbox_max_width() / float(1 << depth); + valid_depth_range(depth_start, depth_end); + + V.clear(); F.clear(); + for (int d = depth_start; d <= depth_end; ++d) { + const int* child_d = children_cpu(d); + const int num = info_->node_num(d); + const float scale = (1 << (depth - d)) * kMul; + + vector pts, normals, pts_ref; + for (int i = 0; i < num; ++i) { + if (node_type(child_d[i]) == kInternelNode && d != depth) continue; + + float n[3], pt[3], pt_ref[3]; + node_normal(n, i, d); + float len = abs(n[0]) + abs(n[1]) + abs(n[2]); + if (len == 0) continue; + node_pos(pt, i, d, pt_ref); + + for (int c = 0; c < 3; ++c) { + normals.push_back(n[c]); + pts.push_back(pt[c]); + pts_ref.push_back(pt_ref[c]); + } + } + + // run marching cube + vector vtx; + vector face; + marching_cube_octree(vtx, face, pts, pts_ref, normals); + + // concate + int nv = V.size() / 3; + for (auto f : face) { + F.push_back(f + nv); + } + + // rescale the vtx and concatenated to V + nv = vtx.size() / 3; + for (int i = 0; i < nv; ++i) { + for (int c = 0; c < 3; ++c) { + float vl = vtx[i * 3 + c] * scale + bbmin[c]; + V.push_back(vl); + } + } + } +} + + +//// only work for adaptive octree +//void Octree::extrapolate_signal() { +// const int depth = info().depth(); +// const int full_layer = info().full_layer(); +// const float imul = 2.0f / sqrtf(3.0f); +// const int channel_label = info().channel(OctreeInfo::kLabel); +// const int channel_normal = 3; +// const bool has_dis = info().has_displace(); +// const bool has_label = channel_label; +// +// for (int d = depth; d >= full_layer; --d) { +// const int nnum_d = info().node_num(d); +// const int* child_d = children_cpu(d); +// const uint32* keys_d = key_cpu(d); +// float* normal_d = mutable_feature_cpu(d); +// //float* label_d = mutable_label(d); // !!!todo: label +// float* displacement_d = normal_d + 3 * nnum_d; +// +// // For adaptive octree, there is kNonEmptyLeaf, +// // but the original child_d contains only kLeaf and kInternelNode, +// // So we use children_d to mark the kNonEmptyLeaf +// vector children_d(child_d, child_d + nnum_d); +// for (int i = 0; i < nnum_d; ++i) { +// if (d == depth) break; +// if (node_type(children_d[i]) == kLeaf) { +// float n[3] = { 0 }; +// node_normal(n, i, d); +// float len = abs(n[0]) + abs(n[1]) + abs(n[2]); +// if (len != 0) { children_d[i] = -2; } +// } +// } +// +// for (int i = 0; i < nnum_d; ++i) { +// if (node_type(children_d[i]) != kLeaf) continue; +// int id = i % 8; +// int i_base = i - id; +// +// float count = ESP; // the non-empty node number +// for (int j = i_base; j < i_base + 8; ++j) { +// if (node_type(children_d[j]) != kLeaf) count += 1.0f; +// } +// +// vector n_avg(channel_normal, 0.0f); +// for (int j = i_base; j < i_base + 8; ++j) { +// if (node_type(children_d[j]) == kLeaf) continue; +// for (int c = 0; c < channel_normal; ++c) { +// n_avg[c] += normal_d[c * nnum_d + j]; +// } +// } +// +// float ilen = 1.0f / (norm2(n_avg) + ESP); +// for (int c = 0; c < channel_normal; ++c) { +// n_avg[c] *= ilen; +// normal_d[c * nnum_d + i] = n_avg[c]; // output +// } +// +// if (has_dis && count > 0.5f) { +// float xyzs[8][3] = { +// {0, 0, 0}, {0, 0, 1.0f}, {0, 1.0f, 0}, {0, 1.0f, 1.0f}, +// {1.0f, 0, 0}, {1.0f, 0, 1.0f}, {1.0f, 1.0f, 0}, {1.0f, 1.0f, 1.0f}, +// }; +// float dis = 0; +// for (int j = i_base; j < i_base + 8; ++j) { +// if (node_type(children_d[j]) == kLeaf) continue; +// dis += displacement_d[j]; +// for (int c = 0; c < channel_normal; ++c) { +// dis += normal_d[c * nnum_d + j] * (xyzs[j % 8][c] - xyzs[id][c]) * imul; +// } +// } +// dis /= count; +// if (dis > 3.0f) dis = 3.0f; +// if (dis < -3.0f) dis = -3.0f; +// if (abs(dis) < 1.0f) { +// // make the voxel has no intersection with the current voxel +// uint32 cube_cases = 0; +// for (int k = 0; k < 8; ++k) { +// float fval = dis; +// for (int j = 0; j < 3; ++j) { +// fval += (0.5 - MarchingCube::corner_[k][j]) * n_avg[j] * imul; +// } +// if (fval < 0) cube_cases |= (1 << k); +// } +// if (cube_cases != 255 && cube_cases != 0) { +// dis = dis < 0 ? -1.0f : 1.0f; +// } +// } +// +// displacement_d[i] = dis; +// } +// +// if (has_dis && count < 0.5f) { +// // find the closest point +// int j_min = -1; +// float pti[3], ptj[3], dis_min = 1.0e30f; +// key2xyz(pti, keys_d[i], d); +// for (int j = 0; j < nnum_d; ++j) { +// if (node_type(children_d[j]) == kLeaf) continue; +// key2xyz(ptj, keys_d[j], d); +// float dis = abs(pti[0] - ptj[0]) + abs(pti[1] - ptj[1]) + abs(pti[2] - ptj[2]); +// if (dis < dis_min) { +// dis_min = dis; +// j_min = j; +// } +// } +// // calc the displacement +// float dis = displacement_d[j_min]; +// key2xyz(ptj, keys_d[j_min], d); +// for (int c = 0; c < channel_normal; ++c) { +// dis += normal_d[c * nnum_d + j_min] * (ptj[c] - pti[c]) * imul; +// } +// if (dis > 0.0f) dis = 2.0f; +// if (dis < 0.0f) dis = -2.0f; +// displacement_d[i] = dis; +// } +// } +// } +//} + + diff --git a/octree/octree/octree.h b/octree/octree/octree.h new file mode 100644 index 0000000..bbc1808 --- /dev/null +++ b/octree/octree/octree.h @@ -0,0 +1,91 @@ +#ifndef _OCTREE_OCTREE_ +#define _OCTREE_OCTREE_ + +#include +#include + +#include "points.h" +#include "octree_info.h" +#include "octree_parser.h" + +using std::vector; +using std::string; + +class Octree : public OctreeParser { + public: + Octree() {} + void set_octree(const Octree& octree_in); // copy from octree_in + void set_octree(vector& data); // swap data and buffer_ + void set_octree(const char* data, const int sz); + void resize_octree(const int sz); + const vector& buffer() const { return buffer_; } + + bool read_octree(const string& filename); + bool write_octree(const string& filename) const; + string get_binary_string() const; + + void build(const OctreeInfo& octree_info, const Points& point_cloud); + void trim_octree(); + + // serialize the results of the function build() into the buffer_ + void serialize(); + bool save_legacy(string& filename); + + void octree2pts(Points& point_cloud, int depth_start, int depth_end, bool rescale = true) const; + void octree2mesh(vector& V, vector& F, int depth_start, int depth_end) const; + + protected: + void clear(int depth = 0); + void normalize_pts(vector& pts_scaled, const Points& pts); + void sort_keys(vector& sorted_keys, vector& sorted_idx, + const vector& pts_scaled); + void unique_key(vector& node_key, vector& pidx); + + void build_structure(vector& node_keys); + void calc_node_num(); // called after the function build_structure() + + void calc_signal(const Points& point_cloud, const vector& pts_scaled, + const vector& sorted_idx, const vector& unique_idx); + void calc_signal(const bool calc_normal_err, const bool calc_dist_err); + void extrapolate_signal(); + + void key_to_xyz(vector >& xyz); + void calc_split_label(); + + template + void serialize(Dtype* des, const vector >& src, const int location); + + void covered_depth_nodes(); + void valid_depth_range(int& depth_start, int& depth_end) const; + + protected: + // the octree is serialized into buffer_ + vector buffer_; + + // Note: structure of arrays(SoA), instead of array of structures(AoS), is + // adopted. The reason is that the SoA is more friendly to GPU-based implementation. + // In the future, probably I will try to implement this class with CUDA accroding + // to this CPU-based code. + vector > keys_; + vector > children_; + + vector > displacement_; + // split_label: 0 - empty; 1 - non-empty, split; 2 - surface-well-approximated + vector > split_labels_; + + vector > avg_normals_; // 3 x N matrix + vector > avg_features_; // 3 x N matrix + vector > avg_pts_; // 3 x N matrix + vector > avg_labels_; + int max_label_; + + OctreeInfo oct_info_; + + // the node number and starting index of depth layer node covered + vector > dnum_; + vector > didx_; + vector > normal_err_; + vector > distance_err_; +}; + +#endif // _OCTREE_OCTREE_ \ No newline at end of file diff --git a/octree/octree/octree_conv.cpp b/octree/octree/octree_conv.cpp new file mode 100644 index 0000000..af48673 --- /dev/null +++ b/octree/octree/octree_conv.cpp @@ -0,0 +1,283 @@ +#include "octree_conv.h" +#include "octree_nn.h" +#include "logs.h" +#include + +namespace octree { + +template +void OctreeBaseConv::setup(const vector& kernel_size, const int stride, + const int curr_depth, const int channel_in, const int channel_out) { + // kernel size + kernel_size_ = kernel_size; + CHECK(kernel_size_[0] < 4 && kernel_size_[1] < 4 && kernel_size_[2] < 4) + << "kernel_size should be less than 4"; + + // stride + stride_ = stride; + CHECK(stride_ <= 2) << "stride should be less than 2"; + + // special case: im2col is the identity for 1x1 convolution with stride 1 + is_1x1_ = kernel_size_[0] == 1 && kernel_size_[1] == 1 && + kernel_size_[2] == 1 && stride_ == 1; + + // current octree depth + curr_depth_ = curr_depth; + + // channels & num_output_ + channels_ = conv_in_channels_ = channel_in; + num_output_ = conv_out_channels_ = channel_out; + if (is_deconvolution_layer()) { + std::swap(conv_out_channels_, conv_in_channels_); + } + + kernel_sdim_ = kernel_size_[0] * kernel_size_[1] * kernel_size_[2]; + kernel_dim_ = kernel_sdim_ * conv_in_channels_; + + ni_cpu_ptr_ = NeighHelper::get_ni(kernel_size_).data(); + ni_gpu_ptr_ = nullptr; // must be set before using +} + + +template +void OctreeBaseConv::reshape() { + // weight shape + weights_shape_ = vector {conv_out_channels_, conv_in_channels_ * kernel_sdim_}; + + // compute top shape + int btm_h = octree_.info().node_num(curr_depth_); + int top_blob_depth = curr_depth_, top_h = btm_h; + if (stride_ == 2) { + if (is_deconvolution_layer()) { + top_blob_depth++; + top_h = octree_.info().node_num(top_blob_depth); + } else { + top_blob_depth--; + top_h = octree_.info().node_num_nempty(top_blob_depth); + } + CHECK(0 <= top_blob_depth && top_blob_depth <= octree_.info().depth()); + } + if (top_h == 0) top_h = 1; // avoid degenerated case + top_shape_ = vector { 1, num_output_, top_h, 1 }; + + // reshape workspce + workspace_depth_ = curr_depth_; // the depth value used for octree2col + if (is_deconvolution_layer() && stride_ == 2) workspace_depth_++; + workspace_h_ = btm_h; + if (stride_ == 2) { + if (is_deconvolution_layer()) { workspace_h_ = top_h >> 3; } + else { workspace_h_ = btm_h >> 3; } + } + workspace_n_ = 1; + workspace_ha_ = workspace_h_; + int ideal_size = workspace_h_ * kernel_dim_; + if (ideal_size > MAX_SIZE && !is_1x1_) { + workspace_n_ = (ideal_size + MAX_SIZE - 1) / MAX_SIZE; + workspace_ha_ = (workspace_h_ + workspace_n_ - 1) / workspace_n_; + } + workspace_shape_ = vector { kernel_dim_, workspace_ha_}; + + // reshape result_buffer_ + if (workspace_n_ > 1) { + result_buffer_shape_ = vector { conv_out_channels_, workspace_ha_ }; + } else { + result_buffer_shape_.clear(); + } + + // reshape data_buffer_ + if (stride_ == 2) { + data_buffer_shape_ = vector { 1, conv_out_channels_, workspace_h_, 1 }; + } else { + data_buffer_shape_.clear(); + } +} + + +template +void OctreeBaseConv::forward_cpu_gemm(Dtype* top_data, + const Dtype* bottom_data, const Dtype* weights) { + const Dtype* col_data = bottom_data; + Dtype* result_data = workspace_n_ == 1 ? top_data : result_buffer_; + for (int n = 0; n < workspace_n_; ++n) { + if (!is_1x1_) { + octree2col_cpu(workspace_, + bottom_data, conv_in_channels_, workspace_h_, kernel_sdim_, + stride_, octree_.neighbor_cpu(workspace_depth_), + ni_cpu_ptr_, workspace_ha_, n); + col_data = workspace_; + } + + engine_cpu_->gemm(false, false, conv_out_channels_, + workspace_ha_, kernel_dim_, Dtype(1.0), weights, col_data, + Dtype(0), result_data); + + if (workspace_n_ == 1) return; + int num = std::min(workspace_ha_, workspace_h_ - n * workspace_ha_); + for (int c = 0; c < conv_out_channels_; ++c) { + memcpy_cpu(num, result_data + c * workspace_ha_, + top_data + c * workspace_h_ + n * workspace_ha_); + } + } +} + +template +void OctreeBaseConv::backward_cpu_gemm(Dtype* bottom_diff, + const Dtype* top_diff, const Dtype* weights) { + Dtype* col_diff = is_1x1_ ? bottom_diff : workspace_; + for (int n = 0; n < workspace_n_; ++n) { + const Dtype* result_buffer = top_diff; + if (workspace_n_ > 1) { + Dtype* buffer_ = result_buffer_; + int num = std::min(workspace_ha_, workspace_h_ - n * workspace_ha_); + for (int c = 0; c < conv_out_channels_; ++c) { + memcpy_cpu(num, top_diff + c * workspace_h_ + n * workspace_ha_, + buffer_ + c * workspace_ha_); + } + result_buffer = result_buffer_; + } + + engine_cpu_->gemm(true, false, kernel_dim_, + workspace_ha_, conv_out_channels_, Dtype(1.0), weights, + result_buffer, Dtype(0.0), col_diff); + + if (!is_1x1_) { + col2octree_cpu(col_diff, bottom_diff, + conv_in_channels_, workspace_h_, kernel_sdim_, + stride_, octree_.neighbor_cpu(workspace_depth_), + ni_cpu_ptr_, workspace_ha_, n); + } + } +} + + +template +void OctreeBaseConv::weight_cpu_gemm(Dtype* weights_diff, + const Dtype* bottom_data, const Dtype* top_diff) { + int num = num_elements(weights_shape_); + memset_cpu(num, Dtype(0), weights_diff); + + const Dtype* col_data = bottom_data; + const Dtype* result_buffer = top_diff; + for (int n = 0; n < workspace_n_; ++n) { + if (!is_1x1_) { + octree2col_cpu(workspace_, + bottom_data, conv_in_channels_, workspace_h_, kernel_sdim_, + stride_, octree_.neighbor_cpu(workspace_depth_), + ni_cpu_ptr_, workspace_ha_, n); + col_data = workspace_; + } + + int num = std::min(workspace_ha_, workspace_h_ - n * workspace_ha_); + if (workspace_n_ > 1) { + Dtype* buffer = result_buffer_; + for (int c = 0; c < conv_out_channels_; ++c) { + memcpy_cpu(num, top_diff + c * workspace_h_ + n * workspace_ha_, + buffer + c * workspace_ha_); + } + result_buffer = result_buffer_; + } + + engine_cpu_->gemm(false, true, conv_out_channels_, + kernel_dim_, workspace_ha_, Dtype(1.0), result_buffer, col_data, + Dtype(1.0), weights_diff); + } +} + +#ifdef USE_CUDA + +template +void OctreeBaseConv::forward_gpu_gemm(Dtype* top_data, + const Dtype* bottom_data, const Dtype* weights) { + const Dtype* col_data = bottom_data; + Dtype* result_data = workspace_n_ == 1 ? top_data : result_buffer_; + for (int n = 0; n < workspace_n_; ++n) { + if (!is_1x1_) { + octree2col_gpu(workspace_, + bottom_data, conv_in_channels_, workspace_h_, kernel_sdim_, + stride_, octree_.neighbor_gpu(workspace_depth_), + ni_gpu_ptr_, workspace_ha_, n); + col_data = workspace_; + } + + engine_gpu_->gemm(false, false, conv_out_channels_, + workspace_ha_, kernel_dim_, Dtype(1.0), weights, col_data, + Dtype(0), result_data); + + if (workspace_n_ == 1) return; + int num = std::min(workspace_ha_, workspace_h_ - n * workspace_ha_); + for (int c = 0; c < conv_out_channels_; ++c) { + memcpy_gpu(num, result_data + c * workspace_ha_, + top_data + c * workspace_h_ + n * workspace_ha_); + } + } +} + +template +void OctreeBaseConv::backward_gpu_gemm(Dtype* bottom_diff, + const Dtype* top_diff, const Dtype* weights) { + Dtype* col_diff = is_1x1_ ? bottom_diff : workspace_; + for (int n = 0; n < workspace_n_; ++n) { + const Dtype* result_buffer = top_diff; + if (workspace_n_ > 1) { + Dtype* buffer_ = result_buffer_; + int num = std::min(workspace_ha_, workspace_h_ - n * workspace_ha_); + for (int c = 0; c < conv_out_channels_; ++c) { + memcpy_gpu(num, top_diff + c * workspace_h_ + n * workspace_ha_, + buffer_ + c * workspace_ha_); + } + result_buffer = result_buffer_; + } + + engine_gpu_->gemm(true, false, kernel_dim_, + workspace_ha_, conv_out_channels_, Dtype(1.0), weights, + result_buffer, Dtype(0.0), col_diff); + + if (!is_1x1_) { + col2octree_gpu(col_diff, bottom_diff, + conv_in_channels_, workspace_h_, kernel_sdim_, + stride_, octree_.neighbor_gpu(workspace_depth_), + ni_gpu_ptr_, workspace_ha_, n); + } + } +} + + +template +void OctreeBaseConv::weight_gpu_gemm(Dtype* weights_diff, + const Dtype* bottom_data, const Dtype* top_diff) { + int num = num_elements(weights_shape_); + memset_gpu(num, Dtype(0), weights_diff); + + const Dtype* col_data = bottom_data; + const Dtype* result_buffer = top_diff; + for (int n = 0; n < workspace_n_; ++n) { + if (!is_1x1_) { + octree2col_gpu(workspace_, + bottom_data, conv_in_channels_, workspace_h_, kernel_sdim_, + stride_, octree_.neighbor_gpu(workspace_depth_), + ni_gpu_ptr_, workspace_ha_, n); + col_data = workspace_; + } + + int num = std::min(workspace_ha_, workspace_h_ - n * workspace_ha_); + if (workspace_n_ > 1) { + Dtype* buffer = result_buffer_; + for (int c = 0; c < conv_out_channels_; ++c) { + memcpy_gpu(num, top_diff + c * workspace_h_ + n * workspace_ha_, + buffer + c * workspace_ha_); + } + result_buffer = result_buffer_; + } + + engine_gpu_->gemm(false, true, conv_out_channels_, + kernel_dim_, workspace_ha_, Dtype(1.0), result_buffer, col_data, + Dtype(1.0), weights_diff); + } +} + +#endif // USE_CUDA + +template class OctreeBaseConv; +template class OctreeBaseConv; + +} // namespace octree diff --git a/octree/octree/octree_conv.h b/octree/octree/octree_conv.h new file mode 100644 index 0000000..de4b20f --- /dev/null +++ b/octree/octree/octree_conv.h @@ -0,0 +1,87 @@ +#ifndef _OCTREE_OCTREE_BASE_CONV_ +#define _OCTREE_OCTREE_BASE_CONV_ + +#include +#include "gemm_engine.h" +#include "octree_parser.h" + +namespace octree { + +using std::vector; + +template +class OctreeBaseConv { + public: + explicit OctreeBaseConv(int max_size = 256 * 1024 * 1024) + : MAX_SIZE(max_size), engine_cpu_(nullptr), engine_gpu_(nullptr) {} + void setup(const vector& kernel_size, const int stride, + const int curr_depth, const int channel_in, const int channel_out); + // after setup() and before reshpae(), + // please set engine_cpu/gpu_, octree_ and ni_gpu_ptr_ + void reshape(); + + protected: + // return true iff we are implementing deconv, so + // that conv helpers know which dimensions are which. + virtual bool is_deconvolution_layer() = 0; + + // Helper functions that abstract away the column buffer and gemm arguments. + void forward_cpu_gemm(Dtype* top_data, const Dtype* bottom_data, + const Dtype* weights); + void backward_cpu_gemm(Dtype* bottom_diff, const Dtype* top_diff, + const Dtype* weights); + void weight_cpu_gemm(Dtype* weights_diff, const Dtype* bottom_data, + const Dtype* top_diff); + + void forward_gpu_gemm(Dtype* top_data, const Dtype* bottom_data, + const Dtype* weights); + void backward_gpu_gemm(Dtype* bottom_diff, const Dtype* top_diff, + const Dtype* weights); + void weight_gpu_gemm(Dtype* weights_diff, const Dtype* bottom_data, + const Dtype* top_diff); + + protected: + int stride_; + vector kernel_size_; + int kernel_dim_; + int kernel_sdim_; // spatial dim of the kernel + bool is_1x1_; + + // input channel & output channel + int channels_; + int num_output_; + + // helper channels for conv and deconv + int conv_out_channels_; + int conv_in_channels_; + + int curr_depth_; + OctreeParser octree_; + + int workspace_n_; + int workspace_ha_; // actual worksapce h + int workspace_h_; // ideal workspace h + int workspace_depth_; + + vector top_shape_; + vector weights_shape_; + vector workspace_shape_; + vector data_buffer_shape_; + vector result_buffer_shape_; + + Dtype* workspace_; + Dtype* data_buffer_; + Dtype* result_buffer_; + + const int* ni_cpu_ptr_; // hold cpu data from NeighHelper::get_ni(kernel_size_) + const int* ni_gpu_ptr_; // hold gpu data from NeighHelper::get_ni(kernel_size_) + + GEMMEngine* engine_cpu_; + GEMMEngine* engine_gpu_; + + int MAX_SIZE; +}; + +} // namespace octree + +#endif // _OCTREE_OCTREE_BASE_CONV_ diff --git a/octree/octree/octree_info.cpp b/octree/octree/octree_info.cpp new file mode 100644 index 0000000..e53462f --- /dev/null +++ b/octree/octree/octree_info.cpp @@ -0,0 +1,271 @@ +#include "octree_info.h" +#include "points.h" + +#include + +const char OctreeInfo::kMagicStr[16] = "_OCTREE_1.0_"; + +void OctreeInfo::initialize(int depth, int full_depth, bool node_displacement, + bool node_feature, bool split_label, bool adaptive, int adaptive_depth, + float threshold_distance, float threshold_normal, bool key2xyz, + bool extrapolate, bool save_pts, const Points& points) { + set_batch_size(1); + set_depth(depth); + set_full_layer(full_depth); + set_adaptive_layer(adaptive_depth); + set_adaptive(adaptive); + set_node_dis(node_displacement); + set_key2xyz(key2xyz); + set_extraplate(extrapolate); + set_save_points(save_pts); + set_threshold_normal(threshold_normal); + set_threshold_dist(threshold_distance); + + // by default, the octree contains Key and Child + int channel = (key2xyz && depth > 8) ? 2 : 1; + set_channel(OctreeInfo::kKey, channel); + set_location(OctreeInfo::kKey, -1); + set_channel(OctreeInfo::kChild, 1); + set_location(OctreeInfo::kChild, -1); + + // set split label + if (split_label) { + set_channel(OctreeInfo::kSplit, 1); + set_location(OctreeInfo::kSplit, -1); + } + + // set feature + const PointsInfo& pt_info = points.info(); + channel = pt_info.channel(PointsInfo::kNormal) + pt_info.channel(PointsInfo::kFeature); + if (node_displacement) { + channel += 1; + // In this case, the difference of the average point with the center of + // octree node (3 channels) are saved replacing normal + if (pt_info.channel(PointsInfo::kNormal) == 0) channel += 3; + } + if (save_pts) { + channel += 3; // save the average points as features + } + set_channel(OctreeInfo::kFeature, channel); + // location = -1 means the features exist on every node + int location = (node_feature || adaptive) ? -1 : depth; + set_location(OctreeInfo::kFeature, location); + + // set label + if (pt_info.channel(PointsInfo::kLabel) == 1) { + // the channel of label is fixed as 1 + set_channel(OctreeInfo::kLabel, 1); + location = (node_feature || adaptive) ? -1 : depth; + set_location(OctreeInfo::kLabel, location); + } + + // init bounding box + bbmin_[0] = bbmin_[1] = bbmin_[2] = -1.0f; + bbmax_[0] = bbmax_[1] = bbmax_[2] = 1.0f; + + // !!! Skip nnum_[], nnum_cum_[], nnum_nempty_[] and ptr_dis_[], + // these three properties can only be set when the octree is built. +} + +void OctreeInfo::reset() { + memset(this, 0, sizeof(OctreeInfo)); + strcpy(magic_str_, kMagicStr); +} + +bool OctreeInfo::check_format(string& msg) const { + msg.clear(); + if (strcmp(kMagicStr, magic_str_) != 0) { + msg += "The version of octree format is not " + string(kMagicStr) + ".\n"; + } + if (batch_size_ < 0) { + msg += "The batch_size_ should be larger than 0.\n"; + } + if (depth_ < 1 || depth_ > 10) { + msg += "The depth_ should be in range [1, 8].\n"; + } + if (full_layer_ < 0 || full_layer_ > depth_) { + msg += "The full_layer_ should be in range [1, depth_].\n"; + } + if (is_adaptive_ && (adp_layer_ < full_layer_ || adp_layer_ > depth_)) { + msg += "The adp_layer_ should be in range [full_layer_, depth_].\n"; + } + const int channel_max[] = { 2, 1, 8, 1 << 30, 1, 1 }; + for (int i = 0; i < kPTypeNum; ++i) { + string str = std::to_string(i); + if (channels_[i] < 0 && channels_[i] > channel_max[i]) { + msg += "The channel " + str + " should be in range [0, " + + std::to_string(channel_max[i]) + "].\n"; + } + if ((channels_[i] == 0) != ((content_flags_ & (1 << i)) == 0)) { + msg += "The content_flags_ should be consistent with channels_[" + str + "].\n"; + } + if (channels_[i] != 0 && locations_[i] != -1 && locations_[i] != depth_) { + msg += "The locations_[" + str + "] should be -1 or " + std::to_string(depth_) + ".\n"; + } + } + + // the OctreeInfo is valid when no error message is produced + return msg.empty(); +} + +bool OctreeInfo::is_consistent(const OctreeInfo& info) const { + // ignore threshold_dist_, threshold_norm_, nnum_, nnum_cum_, + // nnum_nempty_, bbmin_, bbmax_, ptr_dis_ + return strcmp(magic_str_, info.magic_str_) == 0 && + memcmp(channels_, info.channels_, 16 * sizeof(int)) == 0 && + memcmp(locations_, info.locations_, 16 * sizeof(int)) == 0 && + batch_size_ == info.batch_size_ && depth_ == info.depth_ && + full_layer_ == info.full_layer_ && adp_layer_ == info.adp_layer_ && + is_adaptive_ == info.is_adaptive_ && key2xyz_ == info.key2xyz_ && + has_node_dis_ == info.has_node_dis_ && + content_flags_ == info.content_flags_; +} + +int OctreeInfo::channel(PropType ptype) const { + if (!has_property(ptype)) return 0; + int i = property_index(ptype); + return channels_[i]; +} + +int OctreeInfo::locations(PropType ptype) const { + if (!has_property(ptype)) return 0; + int i = property_index(ptype); + return locations_[i]; +} + +int OctreeInfo::ptr_dis(PropType ptype, const int depth) const { + if (!has_property(ptype)) return -1; + int i = property_index(ptype); + int dis = ptr_dis_[i]; + if (locations(ptype) == -1) { + // !!! Note: sizeof(int) + dis += nnum_cum_[depth] * channel(ptype) * sizeof(int); + } else { + // ignore the input parameter depth + } + return dis; +} + +float OctreeInfo::bbox_max_width() const { + float max_width = bbmax_[0] - bbmin_[0]; + for (int i = 1; i < 3; ++i) { + float dis = bbmax_[i] - bbmin_[i]; + if (dis > max_width) max_width = dis; + } + // deal with degenarated case + if (max_width == 0.0f) max_width = 1.0e-10f; + return max_width; +} + +void OctreeInfo::set_content_flags(int cf) { + content_flags_ = cf; +} + +void OctreeInfo::set_batch_size(int b) { + batch_size_ = b < 1 ? 1 : b; +} + +void OctreeInfo::set_depth(int d) { + depth_ = full_layer_ < d ? d : full_layer_; +} + +void OctreeInfo::set_full_layer(int fd) { + full_layer_ = fd < 1 ? 1 : fd; +} + +void OctreeInfo::set_nnum(int d, int num) { + nnum_[d] = num; +} + +void OctreeInfo::set_nnum(const int* num) { + memcpy(nnum_, num, sizeof(int) * (depth_ + 1)); +} + +void OctreeInfo::set_nempty(int d, int num) { + nnum_nempty_[d] = num; +} + +void OctreeInfo::set_nempty(const int* num) { + memcpy(nnum_nempty_, num, sizeof(int) * (depth_ + 1)); +} + +void OctreeInfo::set_nnum_cum(int d, int num) { + nnum_cum_[d] = num; +} + +void OctreeInfo::set_nnum_cum(int capacity) { + nnum_cum_[0] = 0; + for (int d = 1; d < depth_ + 2; ++d) { + nnum_cum_[d] = nnum_cum_[d - 1] + nnum_[d - 1]; + } + nnum_cum_[depth_ + 2] = capacity > nnum_cum_[depth_ + 1] ? + capacity : nnum_cum_[depth_ + 1]; +} + +void OctreeInfo::set_property(PropType ptype, int ch, int lc) { + // this is just a convenient interface to make sure that + // the set_channel and set_location be called together + set_channel(ptype, ch); + set_location(ptype, lc); +} + +void OctreeInfo::set_channel(PropType ptype, int ch) { + // note: the channel and content_flags_ are consisent. + // If channels_[i] != 0, then the i^th bit of content_flags_ is 1. + int i = property_index(ptype); + if (ch > 0) { + channels_[i] = ch; + content_flags_ |= ptype; + } else { + channels_[i] = 0; + // set the corresponding content_flags_ bit as 0 + content_flags_ &= ~ptype; + } +} + +void OctreeInfo::set_location(PropType ptype, int lc) { + // lc: -1, the property exists at all node + // lc: d, the property exists at the d^th level of the octree + // So lc must be in the set [-1, depth] + int i = property_index(ptype); + locations_[i] = lc; +} + +void OctreeInfo::set_ptr_dis() { + // the accumulated pointer displacement + ptr_dis_[0] = sizeof(OctreeInfo); + for (int i = 1; i <= kPTypeNum; ++i) { // note the " <= " is used here + PropType ptype = static_cast(1 << (i - 1)); + int lc = locations(ptype); + int num = lc == -1 ? total_nnum_capacity() : node_num(lc); + // If the property do not exist, lc is equal to 0, then num = 8, both of them + // are meaningless. Their values are wiped out by channels_[i - 1] (= 0). + // So the value of ptr_dis_[i] is still correct. + // !!! Note: sizeof(int) + ptr_dis_[i] = ptr_dis_[i - 1] + sizeof(int) * num * channels_[i - 1]; + } +} + +void OctreeInfo::set_bbox(const float radius, const float* center) { + float bbmin[3] = {-radius + center[0], -radius + center[1], -radius + center[2]}; + float bbmax[3] = {radius + center[0], radius + center[1], radius + center[2]}; + set_bbox(bbmin, bbmax); +} + +void OctreeInfo::set_bbox(const float* bbmin, const float* bbmax) { + const int dim = 3; + for (int i = 0; i < dim; ++i) { + bbmin_[i] = bbmin[i]; + bbmax_[i] = bbmax[i]; + } +} + +int OctreeInfo::property_index(PropType ptype) const { + int k = 0, p = ptype; + for (int i = 0; i < kPTypeNum; ++i) { + if (0 != (p & (1 << i))) { + k = i; break; + } + } + return k; +} diff --git a/octree/octree/octree_info.h b/octree/octree/octree_info.h new file mode 100644 index 0000000..de91d56 --- /dev/null +++ b/octree/octree/octree_info.h @@ -0,0 +1,117 @@ +#ifndef _OCTREE_OCTREE_INFO_ +#define _OCTREE_OCTREE_INFO_ + +#include + +class Points; +using std::string; + +class OctreeInfo { + public: + enum PropType { + kKey = 1, kChild = 2, kNeigh = 4, kFeature = 8, kLabel = 16, kSplit = 32 + }; + static const int kPTypeNum = 6; + static const char kMagicStr[16]; + + public: + OctreeInfo() { reset(); } + + void initialize(int depth, int full_depth, bool node_displacement, bool node_feature, + bool split_label, bool adaptive, int adaptive_depth, float threshold_distance, + float threshold_normal, bool key2xyz, bool extrapolate, bool save_pts, + const Points& points); + + void reset(); + bool check_format(string& msg) const; + bool is_consistent(const OctreeInfo& info) const; + bool has_property(PropType ptype) const { + return (content_flags_ & ptype) != 0; + } + + int batch_size() const { return batch_size_; } + int depth() const { return depth_; } + int full_layer() const { return full_layer_; } + int adaptive_layer() const { return adp_layer_; } + float threshold_distance() const { return threshold_dist_; } + float threshold_normal() const { return threshold_norm_; } + bool is_adaptive() const { return is_adaptive_; } + bool has_displace() const { return has_node_dis_; } + int node_num(int d) const { return nnum_[d]; } + int node_num_cum(int d) const { return nnum_cum_[d]; } + int node_num_nempty(int d) const { return nnum_nempty_[d]; } + int total_nnum() const { return nnum_cum_[depth_ + 1]; } + int total_nnum_capacity() const { return nnum_cum_[depth_ + 2]; } + int content_flags() const { return content_flags_; } + int channel(PropType ptype) const; + int locations(PropType ptype) const; + int ptr_dis(PropType ptype, const int depth) const; + float bbox_max_width() const; + bool is_key2xyz() const { return key2xyz_; } + bool extrapolate() const { return extrapolate_; } + bool save_pts() const { return save_pts_; } + const float* bbmin() const { return bbmin_; } + const float* bbmax() const { return bbmax_; } + // todo: modify sizeof_octinfo() according to magic_str_ for back-compatibility + int sizeof_octinfo() const { return sizeof(OctreeInfo); } + int sizeof_octree() const { return ptr_dis_[kPTypeNum]; } + void set_content_flags(int cf); + void set_batch_size(int b); + void set_depth(int d); + void set_full_layer(int fd); + void set_nnum(int d, int num); + void set_nnum(const int* num); + void set_nempty(int d, int num); + void set_nempty(const int* num); + void set_nnum_cum(int d, int num); + void set_nnum_cum(int capacity = 0); + void set_property(PropType ptype, int ch, int lc); + void set_channel(PropType ptype, int ch); + void set_location(PropType ptype, int lc); + void set_ptr_dis(); + void set_bbox(float radius, const float* center); + void set_bbox(const float* bbmin, const float* bbmax); + void set_key2xyz(bool b) { key2xyz_ = b; } + void set_extraplate(bool b) { extrapolate_ = b; } + void set_save_points(bool b) { save_pts_ = b; } + void set_node_dis(bool dis) { has_node_dis_ = dis; } + void set_adaptive(bool adp) { is_adaptive_ = adp; } + void set_adaptive_layer(int d) { adp_layer_ = d; } + void set_threshold_dist(float th) { threshold_dist_ = th; } + void set_threshold_normal(float th) { threshold_norm_ = th; } + + protected: + int property_index(PropType ptype) const; + + protected: + char magic_str_[16]; + int batch_size_; + int depth_; + int full_layer_; + int adp_layer_; + bool is_adaptive_; + float threshold_dist_; + float threshold_norm_; + bool key2xyz_; + bool has_node_dis_; // if true, the last channel of feature is node displacement + int nnum_[16]; // node number of each depth + int nnum_cum_[16]; // cumulative node number + int nnum_nempty_[16]; // non-empty node number of each depth + int content_flags_; // indicate the existance of a property + int channels_[16]; // signal channel + int locations_[16]; // -1: at all levels; d: at the d^th level + float bbmin_[3]; + float bbmax_[3]; + bool extrapolate_; // added on 2018/12/12, extraplate node feature + bool save_pts_; // added on 2019/01/09, save average points in octree + char reserved_[254]; // reserved for future usage: 2018/12/12 + + private: + int ptr_dis_[16]; +}; + +#endif // _OCTREE_OCTREE_INFO_t + +// bytes need for info: 16*1 + 4*4 + 1(4) + 4*2 + 1*2(2) + 16*4*3 + 4 + 16*4*2 + 3*4*2 + 2 + 254*1 + 16*4 + public: 1 + 4 +// static are omitted +// memory allignment of class in cpp \ No newline at end of file diff --git a/octree/octree/octree_nn.cpp b/octree/octree/octree_nn.cpp new file mode 100644 index 0000000..1235636 --- /dev/null +++ b/octree/octree/octree_nn.cpp @@ -0,0 +1,621 @@ +#include "logs.h" +#include "octree_nn.h" +#include +#include + +void NeighHelper::init_neigh_index() { + const vector > kernel_type{ + { "333", 0 }, { "111", 1 }, { "222", 2 }, + { "311", 3 }, { "131", 4 }, { "113", 5 }, + { "331", 6 }, { "313", 7 }, { "133", 8 } }; + + const vector > vec{ {} /* 333, 27 */, { 13 } /* 111, 1 */, + { 13, 14, 16, 17, 22, 23, 25, 26 } /* 222, 8 */, + { 4, 13, 22 } /* 311, 3 */, + { 10, 13, 16 } /* 131, 3 */, + { 12, 13, 14 } /* 113, 3 */, + { 1, 4, 7, 10, 13, 16, 19, 22, 25 } /* 331, 9 */, + { 3, 4, 5, 12, 13, 14, 21, 22, 23 } /* 313, 9 */, + { 9, 10, 11, 12, 13, 14, 15, 16, 17 } /* 133, 9 */ }; + + // init + ni_map_.insert(kernel_type.begin(), kernel_type.end()); + ni_.resize(kernel_type.size()); + + // ni for kernel_size=333 + ni_[0].assign(216, 0); + int* ni3 = ni_[0].data(); + int id = 0; + for (int ijk = 0; ijk < 8; ++ijk) { + for (int xyz = 0; xyz < 27; ++xyz) { + int k = ijk % 2, p = ijk / 2; + int j = p % 2, i = p / 2; + + int z = xyz % 3, q = xyz / 3; + int y = q % 3, x = q / 3; + + ni3[id++] = ((x + i) << 4) | ((y + j) << 2) | (z + k); + } + } + + // ni for other kernel_sizes + for (int k = 1; k < kernel_type.size(); ++k) { + int sz = vec[k].size(); + ni_[k].assign(8 * sz, 0); + int* ni = ni_[k].data(); + for (int i = 0; i < 8; ++i) { + for (int j = 0; j < sz; ++j) { + ni[i * sz + j] = ni3[i * 27 + vec[k][j]]; + } + } + } + + // init the array parent & displacement + id = 0; + int tmp[64]; + displacement_.assign(64, 0); + int* dis_ptr = displacement_.data(); + for (int x = 1; x < 5; ++x) { + for (int y = 1; y < 5; ++y) { + for (int z = 1; z < 5; ++z) { + int x1 = x / 2; + int xb = x % 2; + int y1 = y / 2; + int yb = y % 2; + int z1 = z / 2; + int zb = z % 2; + + tmp[id] = x1 * 9 + y1 * 3 + z1; + dis_ptr[id] = (xb << 2) | (yb << 1) | zb; + id++; + } + } + } + + parent_.assign(512, 0); + int* parent_ptr = parent_.data(); + for (int i = 0; i < 8; ++i) { + for (int j = 0; j < 64; ++j) { + parent_ptr[i * 64 + j] = ni3[i * 27 + tmp[j]]; + } + } + + // init the bilinear table + bilinear_.assign(512, -1); + const int mask[8][3] = { // bilinear weights: + {0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {1, 0, 0}, // 27, 9, 9, 9 + {0, 1, 1}, {1, 0, 1}, {1, 1, 0}, {1, 1, 1}, // 3, 3, 3, 1 + }; + for (int i = 0; i < 8; ++i) { + // i -> xyz + int z0 = i % 2, t = i / 2; + int y0 = t % 2, x0 = t / 2; + + for (int j = 0; j < 8; ++j) { + // j -> xyz + int z1 = j % 2, s = j / 2; + int y1 = s % 2, x1 = s / 2; + + for (int k = 0; k < 8; ++k) { + int x2 = x0 + 1 + mask[k][0] * (2 * x1 - 1); + int y2 = y0 + 1 + mask[k][1] * (2 * y1 - 1); + int z2 = z0 + 1 + mask[k][2] * (2 * z1 - 1); + + bilinear_[(i << 6) | (j << 3) | k] = (x2 << 4) | (y2 << 2) | z2; + } + } + } +} + + +vector& NeighHelper::get_ni(const vector& kernel_size) { + string key; + CHECK(kernel_size.size() == 3); + for (auto i : kernel_size) key += std::to_string(i); + + auto it = Get().ni_map_.find(key); + CHECK(!(it == Get().ni_map_.end())) << "Unsupported kernel_size"; + return Get().ni_[it->second]; +} + + +template +void memset_cpu(const int N, const Dtype alpha, Dtype* Y) { + if (alpha == 0) { + memset(Y, 0, sizeof(Dtype) * N); + return; + } + for (int i = 0; i < N; ++i) { + Y[i] = alpha; + } +} + + +int num_elements(const vector& vec) { + int count = vec.empty() ? 0 : 1; + for (auto v : vec) { count *= v; } + return count; +} + + +void resize_with_last_val(vector& vec, const int size) { + int len = vec.size(); + if (len == 0) return; + if (len < size) { + int v = vec[len - 1]; + for (int i = len; i < size; ++i) { + vec.push_back(v); + } + } else { + vec.resize(size); + } +} + + +template +void memcpy_cpu(const int N, const Dtype* X, Dtype* Y) { + if (X != Y) { + memcpy(Y, X, sizeof(Dtype) * N); + } +} + + +template +void pad_forward_cpu(Dtype* Y, const int Hy, + const int Cy, const Dtype* X, const int Hx, const int* label, const Dtype dval) { + // Note: Cx == Cy + for (int c = 0; c < Cy; ++c) { + for (int h = 0; h < Hy; ++h) { + Y[c * Hy + h] = label[h] == -1 ? dval : X[c * Hx + label[h]]; + } + } +} + +template +void pad_backward_cpu(Dtype* X, const int Hx, + const int Cx, const Dtype* Y, const int Hy, const int* label) { + // Note: Cx == Cy + for (int c = 0; c < Cx; ++c) { + for (int h = 0; h < Hy; ++h) { + if (label[h] != -1) { + X[c * Hx + label[h]] = Y[c * Hy + h]; + } + } + } +} + + +template +void octree2col_cpu(Dtype* data_col, const Dtype* data_octree, + const int channel, const int height, const int kernel_sdim, + const int stride, const int* neigh, const int* ni, + const int height_col, const int n) { + // height : the ideal height of workspace + // height_col : the actual height of workspace + const int octree_h = height << 3 * (stride - 1); + const int kernel = kernel_sdim; + for (int c = 0; c < channel; ++c) { + for (int k = 0; k < kernel; ++k) { + int h_start = n * height_col; + int i_start = (c * kernel + k) * height_col - h_start; + for (int h = h_start; h < h_start + height_col; ++h) { + if (h >= height) { + data_col[i_start + h] = Dtype(0); + continue; + } + const int index = stride == 2 ? (h << 6) + ni[k] : + (h >> 3 << 6) + ni[(h % 8) * kernel + k]; + const int p = neigh[index]; + data_col[i_start + h] = + p == -1 ? Dtype(0) : data_octree[c * octree_h + p]; + } + } + } +} + +template +void col2octree_cpu(const Dtype* data_col, Dtype* data_octree, + const int channel, const int height, const int kernel_sdim, + const int stride, const int* neigh, const int* ni, + const int height_col, const int n) { + // height : the ideal height of workspace + // height_col : the actual height of workspace + const int octree_h = height << 3 * (stride - 1); + const int kernel = kernel_sdim; + // set data_octree to zero ONCE when n ==0 + if (n == 0) memset_cpu(channel * octree_h, Dtype(0), data_octree); + for (int c = 0; c < channel; ++c) { + for (int k = 0; k < kernel; ++k) { + int h_start = n * height_col; + int i_start = (c * kernel + k) * height_col - h_start; + for (int h = h_start; h < h_start + height_col; ++h) { + if (h >= height) continue; + const int index = stride == 2 ? (h << 6) + ni[k] : + (h >> 3 << 6) + ni[(h % 8) * kernel + k]; + const int p = neigh[index]; + if (p != -1) + data_octree[c * octree_h + p] += data_col[i_start + h]; + } + } + } +} + +template +void octree_max_pool_cpu(Dtype* top_data, int top_h, int* mask, + const Dtype* btm_data, int btm_h, int channel) { + for (int c = 0; c < channel; ++c) { + for (int h = 0; h < top_h; ++h) { + int hb = 8 * h; + top_data[h] = btm_data[hb]; + mask[h] = hb; + for (int idx = hb + 1; idx < hb + 8; ++idx) { + if (btm_data[idx] > top_data[h]) { + top_data[h] = btm_data[idx]; + mask[h] = idx; + } + } + } + + // update pointer + mask += top_h; + top_data += top_h; + btm_data += btm_h; + } +} + +template +void octree_max_unpool_cpu(const Dtype* top_data, int top_h, const int* mask, + Dtype* btm_data, int btm_h, int channel) { + memset_cpu(btm_h * channel, Dtype(0), btm_data); + + for (int c = 0; c < channel; ++c) { + for (int h = 0; h < top_h; ++h) { + btm_data[mask[h]] = top_data[h]; + } + + // update pointer + mask += top_h; + top_data += top_h; + btm_data += btm_h; + } +} + +template +void octree_mask_pool_cpu(Dtype* top_data, int top_h, const int* mask, + const Dtype* btm_data, int btm_h, int channel) { + for (int c = 0; c < channel; ++c) { + for (int h = 0; h < top_h; ++h) { + top_data[h] = btm_data[mask[h]]; + } + + // update pointer + mask += top_h; + top_data += top_h; + btm_data += btm_h; + } +} + + +void calc_neigh_cpu(int* neigh_split, const int* neigh, + const int* children, const int node_num) { + const int* parent = NeighHelper::Get().get_parent_array().data(); + const int* dis = NeighHelper::Get().get_dis_array().data(); + + //#pragma omp parallel for + for (int i = 0; i < node_num; ++i) { + int l0 = children[i]; + if (l0 == -1) continue; + const int* ngh0 = neigh + (i >> 3 << 6); + const int* pi0 = parent + (i % 8) * 64; + int* ngh1 = neigh_split + (l0 << 6); + for (int j = 0; j < 64; ++j) { + ngh1[j] = -1; + int k = ngh0[pi0[j]]; + if (k != -1) { + int l1 = children[k]; + if (l1 != -1) { + ngh1[j] = (l1 << 3) + dis[j]; + } + } + } + } +} + +void calc_neigh_cpu(int* neigh, const int depth, const int batch_size) { + uint32 node_num = 1 << 3 * depth; + const uint32 bound = 1 << depth; + for (uint32 n = 0; n < batch_size; ++n) { + for (uint32 i = 0; i < node_num; i += 8) { + // key to xyz + uint32 x0 = 0, y0 = 0, z0 = 0; + for (uint32 d = 0; d < depth; d++) { + x0 |= (i & (1 << (3 * d + 2))) >> (2 * d + 2); + y0 |= (i & (1 << (3 * d + 1))) >> (2 * d + 1); + z0 |= (i & (1 << (3 * d + 0))) >> (2 * d + 0); + } + + for (uint32 x = 0; x < 4; ++x) { + for (uint32 y = 0; y < 4; ++y) { + for (uint32 z = 0; z < 4; ++z) { + uint32 x1 = x0 + x - 1; + uint32 y1 = y0 + y - 1; + uint32 z1 = z0 + z - 1; + + int v = -1; + if ((x1 & bound) == 0 && + (y1 & bound) == 0 && + (z1 & bound) == 0) { + uint32 key1 = 0; + for (int d = 0; d < depth; d++) { + uint32 mask = 1u << d; + key1 |= ((x1 & mask) << (2 * d + 2)) | + ((y1 & mask) << (2 * d + 1)) | + ((z1 & mask) << (2 * d)); + } + v = key1 + n * node_num; + } + + uint32 xyz = (x << 4) | (y << 2) | z; + neigh[xyz + i * 8 + n * node_num * 8] = v; + } + } + } + } + } +} + + +void generate_key_cpu(uint32* key_child, const uint32* key, const int* child, + const int node_num) { + typedef unsigned char ubyte; + for (int i = 0; i < node_num; ++i) { + int label = child[i]; + if (label == -1) continue; + const ubyte* k0 = (const ubyte*)(key + i); + for (ubyte j = 0; j < 8; ++j) { + ubyte* k1 = (ubyte*)(key_child + 8 * label + j); + k1[0] = (k0[0] << 1) | ((j & 4) >> 2); + k1[1] = (k0[1] << 1) | ((j & 2) >> 1); + k1[2] = (k0[2] << 1) | (j & 1); + k1[3] = k0[3]; + } + } +} + +void generate_key_cpu(uint32* key, const int depth, const int batch_size) { + int node_num = 1 << 3 * depth; + for (int n = 0; n < batch_size; ++n) { + for (int k = 0; k < node_num; ++k) { + unsigned xyz = 0; + unsigned char* ptr = (unsigned char*)(&xyz); + for (int d = 0; d < depth; d++) { + ptr[0] |= (k & (1 << (3 * d + 2))) >> (2 * d + 2); + ptr[1] |= (k & (1 << (3 * d + 1))) >> (2 * d + 1); + ptr[2] |= (k & (1 << (3 * d + 0))) >> (2 * d + 0); + } + ptr[3] = n; + key[n * node_num + k] = xyz; + } + } +} + + +template +void generate_label_cpu(int* label_data, int& top_h, const Dtype* bottom_data, + const int bottom_h, const int mask) { + top_h = 0; + for (int i = 0; i < bottom_h; ++i) { + label_data[i] = (mask == static_cast(bottom_data[i])) ? top_h++ : -1; + } +} + + +void bilinear_neigh_cpu(int* bidx, const int* neigh, const int* child, + const int node_num, const int* table) { + for (int i = 0; i < node_num; ++i) { + int cld = child[i]; + if (cld < 0) continue; // skip empty node + + const int* nghi = neigh + (i >> 3 << 6); + for (int j = 0; j < 8; ++j) { + int k = (cld * 8 + j); // child id + int* des = bidx + k * 8; + const int* tb = table + ((i % 8) * 8 + j) * 8; + for (int k = 0; k < 8; ++k) { + des[k] = nghi[tb[k]]; + } + } + } +} + +void bilinear_xyz_cpu(uint32* xyz0, float* fracs, const int d0, const uint32* xyz1, + const int d1, const int num) { + const float scale = static_cast(1 << (d1 - d0)); + const int mask[8][3] = { // bilinear mask: + {0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {1, 0, 0}, // 27, 9, 9, 9 + {0, 1, 1}, {1, 0, 1}, {1, 1, 0}, {1, 1, 1}, // 3, 3, 3, 1 + }; + + for (int i = 0; i < num; ++i) { + float pt[3] = { 0.0f }; + float* frac = fracs + 3 * i; + int bnd[2][3] = { 0 }; + const unsigned char* ptr1 = (const unsigned char*)(xyz1 + i); + for (int c = 0; c < 3; ++c) { + pt[c] = (static_cast(ptr1[c]) + 0.5f) / scale - 0.5f; + + int b = static_cast(pt[c]); + frac[c] = pt[c] - static_cast(b); + if (frac[c] > 0.5f) { + bnd[0][c] = b + 1; + bnd[1][c] = b; + } else { + frac[c] = 1 - frac[c]; + bnd[0][c] = b; + bnd[1][c] = b + 1; + } + } + + for (int j = 0; j < 8; ++j) { + unsigned char* ptr0 = (unsigned char*)(xyz0 + i * 8 + j); + for (int c = 0; c < 3; ++c) { + ptr0[c] = static_cast(bnd[mask[j][c]][c]); + } + ptr0[3] = ptr1[3]; + } + } +} + + +void search_key_cpu(int* idx, const uint32* key, const int n_key, + const uint32* query, const int n_query) { + for (int i = 0; i < n_query; ++i) { + int j = std::lower_bound(key, key + n_key, query[i]) - key; + idx[i] = (j >= n_key || key[j] != query[i]) ? -1 : j; + } +} + + +void compute_key(uint32& key, const uint32* pt, const int depth) { + key = 0; + for (int i = 0; i < depth; i++) { + uint32 mask = 1u << i; + for (int j = 0; j < 3; j++) { + key |= (pt[j] & mask) << (2 * i + 2 - j); + } + } +} + +void compute_pt(uint32* pt, const uint32& key, const int depth) { + for (int i = 0; i < 3; pt[i++] = 0u); + + for (int i = 0; i < depth; i++) { + for (int j = 0; j < 3; j++) { + // bit mask + uint32 mask = 1u << (3 * i + 2 - j); + // put the bit to position i + pt[j] |= (key & mask) >> (2 * i + 2 - j); + } + } +} + + +// NOTE: !!! currently the depth should be less than 8 +void xyz2key_cpu(uint32* key, const uint32* xyz, const int num, const int depth) { + for (int i = 0; i < num; ++i) { + uint32 pt[3] = { 0, 0, 0 }, key_out = 0; + const unsigned char* ptr = reinterpret_cast(xyz + i); + unsigned char* ptr_out = (unsigned char*)(&key_out); + for (int j = 0; j < 3; ++j) { + pt[j] = static_cast(ptr[j]); + } + compute_key(key_out, pt, depth); + ptr_out[3] = ptr[3]; + key[i] = key_out; + } +} + + +void key2xyz_cpu(uint32* xyz, const uint32* key, const int num, const int depth) { + for (int i = 0; i < num; ++i) { + uint32 pt[3] = { 0 }; + compute_pt(pt, key[i], depth); + + xyz[i] = key[i]; + unsigned char* ptr = reinterpret_cast(xyz + i); + for (int j = 0; j < 3; ++j) { + ptr[j] = static_cast(pt[j]); + } + } +} + + +void key2idx_cpu(int* idx, const uint32* key, const int num) { + for (int i = 0; i < num; ++i) { + const unsigned char* ptr = reinterpret_cast(key + i); + idx[i] = static_cast(ptr[3]); + } +} + + +void xyz2coord_cpu(float* pt, const uint32* xyz, const int num, const int channel) { + for (int i = 0; i < num; ++i) { + const unsigned char* ptr = reinterpret_cast(xyz + i); + for (int c = 0; c < channel; ++c) { + pt[c * num + i] = static_cast(ptr[c]); + } + } +} + + +void coord2xyz_cpu(uint32* xyz, const float* pt, const int num, const int channel) { + for (int i = 0; i < num; ++i) { + unsigned char* ptr = reinterpret_cast(xyz + i); + for (int c = 0; c < channel; ++c) { + ptr[c] = static_cast(pt[c * num + i]); + } + } +} + + +template +void key2xyz(Dtype* xyz, const uint32 key, const int depth) { + uint32 pt[3]; + compute_pt(pt, key, depth); + for (int c = 0; c < 3; ++c) { + xyz[c] = static_cast(pt[c]); + } +} + +// Explicit instantiation +template void memset_cpu(const int N, const int alpha, int* Y); +template void memset_cpu(const int N, const float alpha, float* Y); +template void memset_cpu(const int N, const double alpha, double* Y); +template void memset_cpu(const int N, const char alpha, char* Y); +template void memset_cpu(const int N, const int8_t alpha, int8_t* Y); +template void memset_cpu(const int N, const uint8_t alpha, uint8_t* Y); +template void memcpy_cpu(const int N, const int* X, int* Y); +template void memcpy_cpu(const int N, const unsigned* X, unsigned* Y); +template void memcpy_cpu(const int N, const float* X, float* Y); +template void memcpy_cpu(const int N, const double* X, double* Y); +template void pad_forward_cpu(float* Y, const int Hy, const int Cy, + const float* X, const int Hx, const int* label, const float dval); +template void pad_forward_cpu(double* Y, const int Hy, const int Cy, + const double* X, const int Hx, const int* label, const double dval); +template void pad_backward_cpu(float* X, const int Hx, const int Cx, + const float* Y, const int Hy, const int* label); +template void pad_backward_cpu(double* X, const int Hx, const int Cx, + const double* Y, const int Hy, const int* label); +template void octree2col_cpu(float* data_col, const float* data_octree, + const int channel, const int height, const int kernel_sdim, const int stride, + const int* neigh, const int* ni, const int height_col, const int n); +template void octree2col_cpu(double* data_col, const double* data_octree, + const int channel, const int height, const int kernel_sdim, const int stride, + const int* neigh, const int* ni, const int height_col, const int n); +template void col2octree_cpu(const float* data_col, float* data_octree, + const int channel, const int height, const int kernel_sdim, const int stride, + const int* neigh, const int* ni, const int height_col, const int n); +template void col2octree_cpu(const double* data_col, double* data_octree, + const int channel, const int height, const int kernel_sdim, const int stride, + const int* neigh, const int* ni, const int height_col, const int n); +template void generate_label_cpu(int* label_data, int& top_h, + const float* bottom_data, const int bottom_h, const int mask); +template void generate_label_cpu(int* label_data, int& top_h, + const double* bottom_data, const int bottom_h, const int mask); +template void generate_label_cpu(int* label_data, int& top_h, + const int* bottom_data, const int bottom_h, const int mask); +template void octree_max_pool_cpu(float* top_data, int top_h, + int* mask, const float* btm_data, int bottom_h, int channel); +template void octree_max_pool_cpu(double* top_data, int top_h, + int* mask, const double* btm_data, int bottom_h, int channel); +template void octree_max_unpool_cpu(const float* top_data, int top_h, + const int* mask, float* btm_data, int bottom_h, int channel); +template void octree_max_unpool_cpu(const double* top_data, int top_h, + const int* mask, double* btm_data, int bottom_h, int channel); +template void octree_mask_pool_cpu(float* top_data, int top_h, + const int* mask, const float* btm_data, int bottom_h, int channel); +template void octree_mask_pool_cpu(double* top_data, int top_h, + const int* mask, const double* btm_data, int bottom_h, int channel); +template void key2xyz(float* xyz, const uint32 key, const int d); +template void key2xyz(unsigned* xyz, const uint32 key, const int d); +template void key2xyz(int* xyz, const uint32 key, const int d); \ No newline at end of file diff --git a/octree/octree/octree_nn.cu b/octree/octree/octree_nn.cu new file mode 100644 index 0000000..cbd72ea --- /dev/null +++ b/octree/octree/octree_nn.cu @@ -0,0 +1,817 @@ +#include "octree_nn.h" +#include "device_alternate.h" + +#include +#include +#include +#include +#include +#include + + +template +inline __device__ Dtype caffe_gpu_atomic_add(const Dtype val, Dtype* address); + +template <> +inline __device__ float caffe_gpu_atomic_add(const float val, float* address) { + return atomicAdd(address, val); +} + +// double atomicAdd implementation taken from: +// http://docs.nvidia.com/cuda/cuda-c-programming-guide/#axzz3PVCpVsEG +template <> +inline __device__ double caffe_gpu_atomic_add(const double val, double* address) { + unsigned long long int* address_as_ull = + reinterpret_cast(address); + unsigned long long int old = *address_as_ull; + unsigned long long int assumed; + do { + assumed = old; + old = atomicCAS(address_as_ull, assumed, + __double_as_longlong(val + __longlong_as_double(assumed))); + } while (assumed != old); + return __longlong_as_double(old); +} + + +template +__global__ void memset_kernel(const int n, const Dtype alpha, Dtype* y) { + CUDA_KERNEL_LOOP(index, n) { + y[index] = alpha; + } +} + +template +void memset_gpu(const int N, const Dtype alpha, Dtype* Y) { + if (alpha == 0) { + CUDA_CHECK(cudaMemset(Y, 0, sizeof(Dtype) * N)); + return; + } + memset_kernel <<< CudaGetBlocks(N), kCudaThreadsNum >>> ( + N, alpha, Y); +} + +template +void memcpy_gpu(const int N, const Dtype* X, Dtype* Y) { + if (X != Y && N > 0) { + CUDA_CHECK(cudaMemcpy(Y, X, sizeof(Dtype) * N, cudaMemcpyDefault)); + } +} + + +template +__global__ void pad_forward_kernel(Dtype* Y, const int Hy, + const Dtype* X, const int Hx, const int* label, const int n, const Dtype dval) { + CUDA_KERNEL_LOOP(i, n) { + int h = i % Hy; + int c = i / Hy; + + int idx = label[h]; + Y[i] = idx == -1 ? dval : X[c * Hx + idx]; + } +} + +template +__global__ void pad_backward_kernel(Dtype* X, const int Hx, + const Dtype* Y, const int Hy, const int* label, const int n) { + CUDA_KERNEL_LOOP(i, n) { + int h = i % Hy; + int c = i / Hy; + + int idx = label[h]; + if (idx != -1) { + X[c * Hx + idx] = Y[i]; + } + } +} + +template +void pad_forward_gpu(Dtype* Y, const int Hy, const int Cy, + const Dtype* X, const int Hx, const int* label, const Dtype dval) { + int n = Hy * Cy; // Note: Cx == Cy + pad_forward_kernel <<< CudaGetBlocks(n), kCudaThreadsNum >>> ( + Y, Hy, X, Hx, label, n, dval); + CUDA_POST_KERNEL_CHECK; +} + +template +void pad_backward_gpu(Dtype* X, const int Hx, const int Cx, + const Dtype* Y, const int Hy, const int* label) { + int n = Hy * Cx; // Note: Cx == Cy + pad_backward_kernel <<< CudaGetBlocks(n), kCudaThreadsNum >>> ( + X, Hx, Y, Hy, label, n); + CUDA_POST_KERNEL_CHECK; +} + + +template +__global__ void octree2col_kernel(Dtype* data_col, const Dtype* data_octree, + const int height, const int kernel_dim, const int stride, const int* neigh, + const int* ni, const int height_col, const int n, const int thread_num) { + CUDA_KERNEL_LOOP(i, thread_num) { + int h = i % height_col; + int h1 = h + n * height_col; + if (h1 >= height) { data_col[i] = 0; continue; } + int t = i / height_col; + int k = t % kernel_dim; + int c = t / kernel_dim; + int octree_h = height << 3 * (stride - 1); + + int index = stride == 2 ? (h1 << 6) + ni[k] : + (h1 >> 3 << 6) + ni[(h1 % 8) * kernel_dim + k]; + int p = neigh[index]; + data_col[i] = p == -1 ? Dtype(0) : data_octree[c * octree_h + p]; + } +} + +template +__global__ void col2octree_kernel(const Dtype* data_col, Dtype* data_octree, + const int height, const int kernel_dim, const int stride, const int* neigh, + const int* ni, const int height_col, const int n, const int thread_num) { + CUDA_KERNEL_LOOP(i, thread_num) { + int h = i % height_col; + int h1 = h + n * height_col; + if (h1 >= height) continue; + int t = i / height_col; + int k = t % kernel_dim; + int c = t / kernel_dim; + int octree_h = height << 3 * (stride - 1); + + int index = stride == 2 ? (h1 << 6) + ni[k] : + (h1 >> 3 << 6) + ni[(h1 % 8) * kernel_dim + k]; + int p = neigh[index]; + if (p != -1) caffe_gpu_atomic_add(data_col[i], data_octree + c * octree_h + p); + } +} + +template +void octree2col_gpu(Dtype* data_col, const Dtype* data_octree, + const int channel, const int height, const int kernel_sdim, + const int stride, const int* neigh, const int* ni, + const int height_col, const int n) { + const int kernel = kernel_sdim; + const int thread_num = channel * kernel * height_col; + octree2col_kernel <<< CudaGetBlocks(thread_num), kCudaThreadsNum >>> ( + data_col, data_octree, height, kernel, stride, neigh, ni, height_col, n, thread_num); + CUDA_POST_KERNEL_CHECK; +} + +template +void col2octree_gpu(const Dtype* data_col, Dtype* data_octree, + const int channel, const int height, const int kernel_sdim, + const int stride, const int* neigh, const int* ni, + const int height_col, const int n) { + const int kernel = kernel_sdim; // kernel size: 3*3*3 + const int thread_num = channel * kernel * height_col; + int octree_h = height << 3 * (stride - 1); + // set data_octree to zero ONCE when n ==0 + if (n == 0) memset_gpu(channel * octree_h, Dtype(0), data_octree); + col2octree_kernel <<< CudaGetBlocks(thread_num), kCudaThreadsNum >>> ( + data_col, data_octree, height, kernel, stride, neigh, ni, height_col, n, thread_num); + CUDA_POST_KERNEL_CHECK; +} + + +template +__global__ void octree_max_pool_kernel(Dtype* top_data, const int top_h, + int* mask, const Dtype* btm_data, const int btm_h, const int nthreads) { + CUDA_KERNEL_LOOP(i, nthreads) { + int h = i % top_h; + int c = i / top_h; + + int hb = 8 * h; + int max_idx = hb; + btm_data += c * btm_h; + Dtype max_val = btm_data[hb]; + +#pragma unroll 7 + for (int idx = hb + 1; idx < hb + 8; ++idx) { + Dtype value = btm_data[idx]; + if (value > max_val) { + max_idx = idx; + max_val = value; + } + } + + top_data[i] = max_val; + mask[i] = max_idx; + } +} + +template +void octree_max_pool_gpu(Dtype* top_data, int top_h, int* mask, + const Dtype* btm_data, int btm_h, int channel) { + int num = top_h * channel; + octree_max_pool_kernel <<< CudaGetBlocks(num), kCudaThreadsNum >>> ( + top_data, top_h, mask, btm_data, btm_h, num); + CUDA_POST_KERNEL_CHECK; +} + +template +__global__ void octree_max_unpool_kernel(const Dtype* top_data, const int top_h, + const int* mask, Dtype* btm_data, const int btm_h, const int nthreads) { + CUDA_KERNEL_LOOP(i, nthreads) { + int c = i / top_h; + btm_data[c * btm_h + mask[i]] = top_data[i]; + } +} + +template +void octree_max_unpool_gpu(const Dtype* top_data, int top_h, const int* mask, + Dtype* btm_data, int btm_h, int channel) { + int num = top_h * channel; + memset_gpu(btm_h * channel, Dtype(0), btm_data); + octree_max_unpool_kernel <<< CudaGetBlocks(num), kCudaThreadsNum >>> ( + top_data, top_h, mask, btm_data, btm_h, num); + CUDA_POST_KERNEL_CHECK; +} + +template +__global__ void octree_mask_pool_kernel(Dtype* top_data, const int top_h, + const int* mask, const Dtype* btm_data, const int btm_h, const int nthreads) { + CUDA_KERNEL_LOOP(i, nthreads) { + int c = i / top_h; + top_data[i] = btm_data[c * btm_h + mask[i]]; + } +} + +template +void octree_mask_pool_gpu(Dtype* top_data, int top_h, const int* mask, + const Dtype* btm_data, int btm_h, int channel) { + int num = top_h * channel; + octree_mask_pool_kernel <<< CudaGetBlocks(num), kCudaThreadsNum >>> ( + top_data, top_h, mask, btm_data, btm_h, num); + CUDA_POST_KERNEL_CHECK; +} + + + +__global__ void calc_neigh_kernel(int* neigh_split, const int* neigh, + const int* children, const int* parent, const int* dis, const int thread_num) { + CUDA_KERNEL_LOOP(id, thread_num) { + int i = id >> 6; + int j = id % 64; + + int l0 = children[i]; + if (l0 != -1) { + const int* ngh0 = neigh + (i >> 3 << 6); + const int* pi0 = parent + (i % 8) * 64; + int* ngh1 = neigh_split + (l0 << 6); + int t = -1; + int k = ngh0[pi0[j]]; + if (k != -1) { + int l1 = children[k]; + if (l1 != -1) { + t = (l1 << 3) + dis[j]; + } + } + ngh1[j] = t; + } + } +} + +void calc_neigh_gpu(int* neigh_split, const int* neigh, const int* children, + const int node_num, const int* parent, const int* dis) { + int n = node_num << 6; // node_num: the non_empty node number of parent layer + calc_neigh_kernel <<< CudaGetBlocks(n), kCudaThreadsNum >>> ( + neigh_split, neigh, children, parent, dis, n); +} + +__global__ void calc_full_neigh_kernel(int* neigh, const int depth, + const int batch_size, const int thread_num) { + CUDA_KERNEL_LOOP(id, thread_num) { + const uintk bound = 1 << depth; + uintk node_num = 1 << 3 * depth; + uintk num = node_num >> 3; + + uintk tm = id; + uintk z = tm % 4; tm /= 4; + uintk y = tm % 4; tm /= 4; + uintk x = tm % 4; tm /= 4; + uintk i = (tm % num) * 8; + uintk n = tm / num; + + uintk x0 = 0, y0 = 0, z0 = 0; +#pragma unroll 4 + for (uintk d = 0; d < depth; d++) { + x0 |= (i & (1 << 3 * d + 2)) >> (2 * d + 2); + y0 |= (i & (1 << 3 * d + 1)) >> (2 * d + 1); + z0 |= (i & (1 << 3 * d + 0)) >> (2 * d + 0); + } + + uintk x1 = x0 + x - 1; + uintk y1 = y0 + y - 1; + uintk z1 = z0 + z - 1; + + int v = -1; + if ((x1 & bound) == 0 && + (y1 & bound) == 0 && + (z1 & bound) == 0) { + uintk key1 = 0; +#pragma unroll 4 + for (int d = 0; d < depth; d++) { + uintk mask = 1u << d; + key1 |= ((x1 & mask) << (2 * d + 2)) | + ((y1 & mask) << (2 * d + 1)) | + ((z1 & mask) << (2 * d)); + } + v = key1 + n * node_num; + } + + neigh[id] = v; + } +} + +void calc_neigh_gpu(int* neigh, const int depth, const int batch_size) { + int thread_num = batch_size * (1 << 3 * depth + 3); + calc_full_neigh_kernel <<< CudaGetBlocks(thread_num), kCudaThreadsNum >>> ( + neigh, depth, batch_size, thread_num); + CUDA_POST_KERNEL_CHECK; +} + + +template +__global__ void gen_key_kernel(Dtype* key_child, const Dtype* key, + const int* child, const int thread_num) { + typedef typename KeyTrait::uints T; + CUDA_KERNEL_LOOP(id, thread_num) { + int i = id >> 3; + int j = id % 8; + + int label = child[i]; + if (label != -1) { + const T* k0 = (const T*)(key + i); + T* k1 = (T*)(key_child + 8 * label + j); + k1[0] = (k0[0] << 1) | ((j & 4) >> 2); + k1[1] = (k0[1] << 1) | ((j & 2) >> 1); + k1[2] = (k0[2] << 1) | (j & 1); + k1[3] = k0[3]; + } + } +} + +// use the information from parent layer to calculate the key of current layer +template +void generate_key_gpu(Dtype* key_child, const Dtype* key, const int* child, + const int node_num) { + int n = node_num << 3; // node_num: the node number of parent layer + gen_key_kernel <<< CudaGetBlocks(n), kCudaThreadsNum >>> ( + key_child, key, child, n); + CUDA_POST_KERNEL_CHECK; +} + +template +__global__ void gen_full_key_kernel(Dtype* key, const int depth, + const int batch_size, const int thread_num) { + typedef typename KeyTrait::uints T; + CUDA_KERNEL_LOOP(i, thread_num) { + Dtype node_num = 1 << 3 * depth; + Dtype k = i % node_num; + Dtype xyz = 0; + T* ptr = (T*)(&xyz); +#pragma unroll 8 + for (int d = 0; d < depth; d++) { + ptr[0] |= (k & (1 << 3 * d + 2)) >> (2 * d + 2); + ptr[1] |= (k & (1 << 3 * d + 1)) >> (2 * d + 1); + ptr[2] |= (k & (1 << 3 * d + 0)) >> (2 * d + 0); + } + ptr[3] = i / node_num; + key[i] = xyz; + } +} + +template +void generate_key_gpu(Dtype* key, const int depth, const int batch_size) { + int thread_num = batch_size * (1 << 3 * depth); + gen_full_key_kernel <<< CudaGetBlocks(thread_num), kCudaThreadsNum >>> ( + key, depth, batch_size, thread_num); + CUDA_POST_KERNEL_CHECK; +} + + +template +void generate_label_gpu(int* label_data, int& top_h, const Dtype* btm_data, + const int btm_h, const int mask) { + top_h = 0; + thrust::transform_exclusive_scan(thrust::device, btm_data, btm_data + btm_h, + label_data, mask == thrust::placeholders::_1, 0, thrust::plus()); + cudaMemcpy(&top_h, label_data + btm_h - 1, sizeof(int), cudaMemcpyDeviceToHost); + Dtype flag = -1; + cudaMemcpy(&flag, btm_data + btm_h - 1, sizeof(Dtype), cudaMemcpyDeviceToHost); + if (mask == flag) top_h++; + thrust::replace_if(thrust::device, label_data, label_data + btm_h, btm_data, + mask != thrust::placeholders::_1, -1); +} + + +__global__ void bilinear_neigh_kernel(int* bidx, const int* neigh, const int* child, + const int node_num, const int* table) { + CUDA_KERNEL_LOOP(i, node_num) { + int cld = child[i]; + if (cld < 0) continue; // skip empty node + const int* nghi = neigh + (i >> 3 << 6); +#pragma unroll 8 + for (int j = 0; j < 8; ++j) { + int k = (cld * 8 + j); // child id + int* des = bidx + k * 8; + const int* tb = table + ((i % 8) * 8 + j) * 8; + for (int k = 0; k < 8; ++k) { + des[k] = nghi[tb[k]]; + } + } + } +} + + +void bilinear_neigh_gpu(int* bidx, const int* neigh, const int* child, + const int node_num, const int* table) { + bilinear_neigh_kernel <<< CudaGetBlocks(node_num), kCudaThreadsNum >>> ( + bidx, neigh, child, node_num, table); + CUDA_POST_KERNEL_CHECK; +} + + +__global__ void bilinear_xyz_kernel(uintk* xyz0, float* fracs, + const uintk* xyz1, const float scale, const int num) { + typedef typename KeyTrait::uints uints; + const int mask[8][3] = { // bilinear mask: + {0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {1, 0, 0}, // 27, 9, 9, 9 + {0, 1, 1}, {1, 0, 1}, {1, 1, 0}, {1, 1, 1}, // 3, 3, 3, 1 + }; + + CUDA_KERNEL_LOOP(i, num) { + float pt[3] = { 0.0f }; + float* frac = fracs + 3 * i; + int bnd[2][3] = { 0 }; + const uints* ptr1 = (const uints*)(xyz1 + i); +#pragma unroll 3 + for (int c = 0; c < 3; ++c) { + pt[c] = (static_cast(ptr1[c]) + 0.5f) / scale - 0.5f; + + int b = static_cast(pt[c]); + frac[c] = pt[c] - static_cast(b); + if (frac[c] > 0.5f) { + bnd[0][c] = b + 1; + bnd[1][c] = b; + } else { + frac[c] = 1 - frac[c]; + bnd[0][c] = b; + bnd[1][c] = b + 1; + } + } + +#pragma unroll 8 + for (int j = 0; j < 8; ++j) { + uints* ptr0 = (uints*)(xyz0 + i * 8 + j); + for (int c = 0; c < 3; ++c) { + ptr0[c] = static_cast(bnd[mask[j][c]][c]); + } + ptr0[3] = ptr1[3]; + } + } +} + + +void bilinear_xyz_gpu(uintk* xyz0, float* fracs, const int d0, const uintk* xyz1, + const int d1, const int num) { + const float scale = static_cast(1 << (d1 - d0)); + bilinear_xyz_kernel <<< CudaGetBlocks(num), kCudaThreadsNum >>> ( + xyz0, fracs, xyz1, scale, num); + CUDA_POST_KERNEL_CHECK; +} + + +template +void sequence_gpu(Dtype* ptr, const int num) { + thrust::sequence(thrust::device, ptr, ptr + num); +} + + +template +__global__ void validate_search_kernel(int* idx, const Dtype* key, const int n_key, + const Dtype* query, const int n_query) { + CUDA_KERNEL_LOOP(i, n_query) { + int j = idx[i]; + if (j >= n_key || key[j] != query[i]) idx[i] = -1; + } +} + +template +void search_key_gpu(int* idx, const Dtype* key, const int n_key, + const Dtype* query, const int n_query) { + thrust::lower_bound(thrust::device, key, key + n_key, query, query + n_query, idx); + validate_search_kernel <<< CudaGetBlocks(n_query), kCudaThreadsNum >>> ( + idx, key, n_key, query, n_query); + CUDA_POST_KERNEL_CHECK; +} + + +template +__global__ void xyz2key_kernel(Dtype* key, const Dtype* xyz, + const int num, const int depth) { + typedef typename KeyTrait::uints T; + + CUDA_KERNEL_LOOP(i, num) { + Dtype xyz_in = xyz[i]; + Dtype key_out = 0; + T* ptr = (T*)(&xyz_in); + T* ptr_out = (T*)(&key_out); +#pragma unroll 8 + for (int d = 0; d < depth; ++d) { + T mask = 1 << d; + key_out |= (ptr[0] & mask) << (2 * d + 2) | + (ptr[1] & mask) << (2 * d + 1) | + (ptr[2] & mask) << (2 * d + 0); + } + ptr_out[3] = ptr[3]; + key[i] = key_out; + } +} + +template +void xyz2key_gpu(Dtype* key, const Dtype* xyz, const int num, const int depth) { + xyz2key_kernel <<< CudaGetBlocks(num), kCudaThreadsNum >>> ( + key, xyz, num, depth); + CUDA_POST_KERNEL_CHECK; +} + +template +__global__ void key2xyz_kernel(Dtype* xyz, const Dtype* key, + const int num, const int depth) { + typedef typename KeyTrait::uints T; + + CUDA_KERNEL_LOOP(i, num) { + Dtype key_in = key[i], xyz_out = 0; + T* pt = (T*)(&xyz_out); + T* ptr = (T*)(&key_in); + pt[3] = ptr[3]; +#pragma unroll 8 + for (int d = 0; d < depth; d++) { + pt[0] |= (key_in & (1u << (3 * d + 2))) >> (2 * d + 2); + pt[1] |= (key_in & (1u << (3 * d + 1))) >> (2 * d + 1); + pt[2] |= (key_in & (1u << (3 * d))) >> (2 * d); + } + + xyz[i] = xyz_out; + } +} + +template +void key2xyz_gpu(Dtype* xyz, const Dtype* key, const int num, const int depth) { + key2xyz_kernel <<< CudaGetBlocks(num), kCudaThreadsNum >>> ( + xyz, key, num, depth); + CUDA_POST_KERNEL_CHECK; +} + +template +__global__ void key2idx_kernel(int* idx, const Dtype* key, const int num) { + typedef typename KeyTrait::uints T; + + CUDA_KERNEL_LOOP(i, num) { + const T* ptr = (const T*)(key + i); + idx[i] = static_cast(ptr[3]); + } +} + +template +void key2idx_gpu(int* idx, const Dtype* key, const int num) { + key2idx_kernel <<< CudaGetBlocks(num), kCudaThreadsNum >>> (idx, key, num); + CUDA_POST_KERNEL_CHECK; +} + + +template +__global__ void xyz2coord_kernel(float* pt, const Dtype* xyz, const int num, + const int nthreads) { + typedef typename KeyTrait::uints T; + + CUDA_KERNEL_LOOP(i, nthreads) { + int h = i % num, c = i / num; + const T* ptr = (const T*)(xyz + h); + pt[i] = static_cast(ptr[c]); + } +} + +template +void xyz2coord_gpu(float* pt, const Dtype* xyz, const int num, const int channel) { + int nthreads = num * channel; + xyz2coord_kernel <<< CudaGetBlocks(nthreads), kCudaThreadsNum >>> ( + pt, xyz, num, nthreads); + CUDA_POST_KERNEL_CHECK; +} + + +template +__global__ void coord2xyz_kernel(Dtype* xyz, const float* pt, const int num, + const int nthreads) { + typedef typename KeyTrait::uints T; + + CUDA_KERNEL_LOOP(i, nthreads) { + int h = i % num, c = i / num; + T* ptr = (T*)(xyz + h); + ptr[c] = static_cast(pt[i]); + } +} + +template +void coord2xyz_gpu(Dtype* xyz, const float* pt, const int num, const int channel) { + int nthreads = num * channel; + coord2xyz_kernel <<< CudaGetBlocks(nthreads), kCudaThreadsNum >>> ( + xyz, pt, num, nthreads); + CUDA_POST_KERNEL_CHECK; +} + + +template +__global__ void align_forward_kernel(Dtype* top_data, const int top_h, + const Dtype* btm_data, const int btm_h, const int* index_data, + const int btm_num) { + CUDA_KERNEL_LOOP(i, btm_num) { + int h = i % btm_h; + int c = i / btm_h; + int j = index_data[h]; + if (j != -1) { + top_data[c * top_h + j] = btm_data[i]; + } + } +} + +template +void align_forward_gpu(Dtype* top_data, const int top_h, const int channel, + const Dtype* btm_data, const int btm_h, const int* idx) { + int btm_num = btm_h * channel; + memset_gpu(top_h * channel, Dtype(0), top_data); + align_forward_kernel <<< CudaGetBlocks(btm_num), kCudaThreadsNum >>> ( + top_data, top_h, btm_data, btm_h, idx, btm_num); + CUDA_POST_KERNEL_CHECK; +} + +template +__global__ void align_backward_kernel(const Dtype* top_data, const int top_h, + Dtype* btm_data, const int btm_h, const int* index_data, const int btm_num) { + CUDA_KERNEL_LOOP(i, btm_num) { + int h = i % btm_h; + int c = i / btm_h; + int j = index_data[h]; + btm_data[i] = j == -1 ? 0 : top_data[c * top_h + j]; + } +} + +template +void align_backward_gpu(const Dtype* top_data, const int top_h, const int channel, + Dtype* btm_data, const int btm_h, const int* idx) { + int btm_num = btm_h * channel; + align_backward_kernel <<< CudaGetBlocks(btm_num), kCudaThreadsNum >>> ( + top_data, top_h, btm_data, btm_h, idx, btm_num); + CUDA_POST_KERNEL_CHECK; +} + + +template +__global__ void octree_gather_kernel(Dtype* top_data, const int top_h, + const Dtype* btm_data, const int btm_h, const int* index_data, const int num) { + CUDA_KERNEL_LOOP(i, num) { + int h = i % top_h; + int c = i / top_h; + int j = index_data[h]; + if (j != -1) { + top_data[i] = btm_data[c * btm_h + j]; + } + } +} + +template +void octree_gather_gpu(Dtype* top_data, const int top_h, const int channel, + const Dtype* btm_data, const int btm_h, const int* idx) { + pad_forward_gpu(top_data, top_h, channel, btm_data, btm_h, idx, Dtype(0)); + + //int num = top_h * channel; + //memset_gpu(num, Dtype(0), top_data); + //octree_gather_kernel <<< CudaGetBlocks(num), kCudaThreadsNum >>> ( + // top_data, top_h, btm_data, btm_h, idx, num); + //CUDA_POST_KERNEL_CHECK; +} + +template +__global__ void octree_gatherbk_kernel(const Dtype* top_data, const int top_h, + Dtype* btm_data, const int btm_h, const int* index_data, const int num) { + CUDA_KERNEL_LOOP(i, num) { + int h = i % top_h; + int c = i / top_h; + int j = index_data[h]; + if (j != -1) { + caffe_gpu_atomic_add(top_data[i], btm_data + c * btm_h + j); + } + } +} + +template +void octree_gatherbk_gpu(const Dtype* top_data, const int top_h, const int channel, + Dtype* btm_data, const int btm_h, const int* idx) { + int num = top_h * channel; + memset_gpu(channel * btm_h, Dtype(0), btm_data); + octree_gatherbk_kernel <<< CudaGetBlocks(num), kCudaThreadsNum >>> ( + top_data, top_h, btm_data, btm_h, idx, num); + CUDA_POST_KERNEL_CHECK; +} + + + +__global__ void octree_mask_kernel(float* des, const float* src, + const int* label_data, const int height, const int mask, const int n) { + CUDA_KERNEL_LOOP(i, n) { + int h = i % height; + des[i] = label_data[h] == mask ? float(0) : src[i]; + } +} + +void octree_mask_gpu(float* out_data, const float* in_data, const int* label, + int height, int mask, int num) { + octree_mask_kernel <<< CudaGetBlocks(num), kCudaThreadsNum >>> ( + out_data, in_data, label, height, mask, num); + CUDA_POST_KERNEL_CHECK; +} + + +// Explicit instantiation +template void memset_gpu(const int N, const int alpha, int* Y); +template void memset_gpu(const int N, const float alpha, float* Y); +template void memset_gpu(const int N, const double alpha, double* Y); +template void memset_gpu(const int N, const char alpha, char* Y); +template void memset_gpu(const int N, const int8_t alpha, int8_t* Y); +template void memset_gpu(const int N, const uint8_t alpha, uint8_t* Y); +template void memcpy_gpu(const int N, const int* X, int* Y); +template void memcpy_gpu(const int N, const uint32* X, uint32* Y); +template void memcpy_gpu(const int N, const uint64* X, uint64* Y); +template void memcpy_gpu(const int N, const float* X, float* Y); +template void memcpy_gpu(const int N, const double* X, double* Y); +template void sequence_gpu(int* ptr, const int num); +template void sequence_gpu(uintk* ptr, const int num); +template void pad_forward_gpu(float* Y, const int Hy, const int Cy, + const float* X, const int Hx, const int* label, const float dval); +template void pad_forward_gpu(double* Y, const int Hy, const int Cy, + const double* X, const int Hx, const int* label, const double dval); +template void pad_backward_gpu(float* X, const int Hx, const int Cx, + const float* Y, const int Hy, const int* label); +template void pad_backward_gpu(double* X, const int Hx, const int Cx, + const double* Y, const int Hy, const int* label); +template void octree2col_gpu(float* data_col, const float* data_octree, + const int channel, const int height, const int kernel_sdim, const int stride, + const int* neigh, const int* ni, const int height_col, const int n); +template void octree2col_gpu(double* data_col, const double* data_octree, + const int channel, const int height, const int kernel_sdim, const int stride, + const int* neigh, const int* ni, const int height_col, const int n); +template void col2octree_gpu(const float* data_col, float* data_octree, + const int channel, const int height, const int kernel_sdim, const int stride, + const int* neigh, const int* ni, const int height_col, const int n); +template void col2octree_gpu(const double* data_col, double* data_octree, + const int channel, const int height, const int kernel_sdim, const int stride, + const int* neigh, const int* ni, const int height_col, const int n); +template void generate_label_gpu(int* label_data, int& top_h, + const float* bottom_data, const int bottom_h, const int mask); +template void generate_label_gpu(int* label_data, int& top_h, + const double* bottom_data, const int bottom_h, const int mask); +template void generate_label_gpu(int* label_data, int& top_h, + const int* bottom_data, const int bottom_h, const int mask); +template void octree_max_pool_gpu(float* top_data, int top_h, + int* mask, const float* btm_data, int bottom_h, int channel); +template void octree_max_pool_gpu(double* top_data, int top_h, + int* mask, const double* btm_data, int bottom_h, int channel); +template void octree_max_unpool_gpu(const float* top_data, int top_h, + const int* mask, float* btm_data, int bottom_h, int channel); +template void octree_max_unpool_gpu(const double* top_data, int top_h, + const int* mask, double* btm_data, int bottom_h, int channel); +template void octree_mask_pool_gpu(float* top_data, int top_h, + const int* mask, const float* btm_data, int bottom_h, int channel); +template void octree_mask_pool_gpu(double* top_data, int top_h, + const int* mask, const double* btm_data, int bottom_h, int channel); +template void align_forward_gpu(float* top_data, const int top_h, const int c, + const float* btm_data, const int btm_h, const int* idx); +template void align_forward_gpu(double* top_data, const int top_h, const int c, + const double* btm_data, const int btm_h, const int* idx); +template void align_backward_gpu(const float* top_data, const int top_h, + const int c, float* btm_data, const int btm_h, const int* idx); +template void align_backward_gpu(const double* top_data, const int top_h, + const int c, double* btm_data, const int btm_h, const int* idx); +template void octree_gather_gpu(float* top_data, const int top_h, const int c, + const float* btm_data, const int btm_h, const int* idx); +template void octree_gather_gpu(double* top_data, const int top_h, const int c, + const double* btm_data, const int btm_h, const int* idx); +template void octree_gatherbk_gpu(const float* top_data, const int top_h, + const int c, float* btm_data, const int btm_h, const int* idx); +template void octree_gatherbk_gpu(const double* top_data, const int top_h, + const int c, double* btm_data, const int btm_h, const int* idx); +template void generate_key_gpu(uintk* key, const int depth, const int batch_size); +template void generate_key_gpu(uintk* key_child, const uintk* key, + const int* child, const int node_num); +template void search_key_gpu(int* idx, const uintk* key, const int n_key, + const uintk* query, const int n_query); +template void xyz2key_gpu(uintk* key, const uintk* xyz, const int num, + const int depth); +template void key2xyz_gpu(uintk* xyz, const uintk* key, const int num, + const int depth); +template void key2idx_gpu(int* idx, const uintk* key, const int num); +template void xyz2coord_gpu(float* pt, const uintk* xyz, const int num, + const int channel); +template void coord2xyz_gpu(uintk* xyz, const float* pt, const int num, + const int channel); diff --git a/octree/octree/octree_nn.h b/octree/octree/octree_nn.h new file mode 100644 index 0000000..545edec --- /dev/null +++ b/octree/octree/octree_nn.h @@ -0,0 +1,214 @@ +#ifndef _OCTREE_OCTREE_NN_ +#define _OCTREE_OCTREE_NN_ + +#include +#include +#include +#include + +using std::string; +using std::vector; +using std::unordered_map; +typedef uint32_t uint32; + + +// A singleton class to hold global & common stuff for octree neighbor +class NeighHelper { + public: + static NeighHelper& Get() { + static NeighHelper instance; // Guaranteed to be destroyed. + return instance; // Instantiated on first use. + } + + static vector& get_parent_array() { return Get().parent_; } + static vector& get_dis_array() { return Get().displacement_; } + static vector& get_bilinear_array() { return Get().bilinear_; } + static vector& get_ni(const vector& kernel_size); + + private: + NeighHelper() { init_neigh_index(); } + void init_neigh_index(); + + // avoid accidentally getting copies of your singleton appearing. + NeighHelper(NeighHelper const&); + void operator=(NeighHelper const&); + + protected: + // used to get the neighbor information + vector > ni_; + unordered_map ni_map_; + + // used to calculate the neighbor information + vector parent_; + vector displacement_; + + // used to bilinear interpolation + vector bilinear_; +}; + + +// !!! TODO: support gpu stream for gpu functions !!! + +int num_elements(const vector& vec); +void resize_with_last_val(vector& vec, const int size); + +template +void memset_cpu(const int N, const Dtype alpha, Dtype *X); +template +void memset_gpu(const int N, const Dtype alpha, Dtype *X); +template +void memcpy_cpu(const int N, const Dtype* X, Dtype* Y); +template +void memcpy_gpu(const int N, const Dtype* X, Dtype* Y); + + +template +void pad_forward_cpu(Dtype* Y, const int Hy, const int Cy, + const Dtype* X, const int Hx, const int* label, const Dtype dval = 0); +template +void pad_forward_gpu(Dtype* Y, const int Hy, const int Cy, + const Dtype* X, const int Hx, const int* label, const Dtype dval = 0); +template +void pad_backward_cpu(Dtype* X, const int Hx, const int Cx, + const Dtype* Y, const int Hy, const int* label); +template +void pad_backward_gpu(Dtype* X, const int Hx, const int Cx, + const Dtype* Y, const int Hy, const int* label); + + +template +void octree2col_cpu(Dtype* data_col, const Dtype* data_octree, + const int channel, const int height, const int kernel_sdim, + const int stride, const int* neigh, const int* ni, + const int height_col, const int n); +template +void octree2col_gpu(Dtype* data_col, const Dtype* data_octree, + const int channel, const int height, const int kernel_sdim, + const int stride, const int* neigh, const int* ni, + const int height_col, const int n); +template +void col2octree_cpu(const Dtype* data_col, Dtype* data_octree, + const int channel, const int height, const int kernel_sdim, + const int stride, const int* neigh, const int* ni, + const int height_col, const int n); +template +void col2octree_gpu(const Dtype* data_col, Dtype* data_octree, + const int channel, const int height, const int kernel_sdim, + const int stride, const int* neigh, const int* ni, + const int height_col, const int n); + + +template +void octree_max_pool_cpu(Dtype* top_data, int top_h, int* mask, + const Dtype* btm_data, int btm_h, int channel); +template +void octree_max_pool_gpu(Dtype* top_data, int top_h, int* mask, + const Dtype* btm_data, int btm_h, int channel); +template +void octree_max_unpool_cpu(const Dtype* top_diff, int top_h, const int* mask, + Dtype* btm_diff, int btm_h, int channel); +template +void octree_max_unpool_gpu(const Dtype* top_diff, int top_h, const int* mask, + Dtype* btm_diff, int btm_h, int channel); +template +void octree_mask_pool_cpu(Dtype* top_data, int top_h, const int* mask, + const Dtype* btm_data, int btm_h, int channel); +template +void octree_mask_pool_gpu(Dtype* top_data, int top_h, const int* mask, + const Dtype* btm_data, int btm_h, int channel); + + +void calc_neigh_cpu(int* neigh_child, const int* neigh, const int* child, + const int node_num); +void calc_neigh_gpu(int* neigh_child, const int* neigh, const int* children, + const int node_num, const int* parent, const int* dis); +void calc_neigh_cpu(int* neigh, const int depth, const int batch_size); +void calc_neigh_gpu(int* neigh, const int depth, const int batch_size); + + +void generate_key_gpu(uint32* key_child, const uint32* key, const int* child, + const int node_num); +void generate_key_cpu(uint32* key_child, const uint32* key, const int* child, + const int node_num); +void generate_key_gpu(uint32* key, const int depth, const int batch_size); +void generate_key_cpu(uint32* key, const int depth, const int batch_size); + + +template +void generate_label_cpu(int* label_data, int& top_h, const Dtype* bottom_data, + const int bottom_h, const int mask); +template +void generate_label_gpu(int* label_data, int& top_h, const Dtype* bottom_data, + const int bottom_h, const int mask); + + +void bilinear_neigh_cpu(int* bidx, const int* neigh, const int* child, + const int node_num, const int* table); +void bilinear_neigh_gpu(int* bidx, const int* neigh, const int* child, + const int node_num, const int* table); +void bilinear_xyz_cpu(uint32* xyz0, float* dis, const int d0, const uint32* xyz1, + const int d1, const int num); +void bilinear_xyz_gpu(uint32* xyz0, float* dis, const int d0, const uint32* xyz1, + const int d1, const int num); + + +template +void sequence_gpu(Dtype* ptr, const int num); + + +// TODO: The performance can be improved via descent along the octree +void search_key_cpu(int* idx, const uint32* key, const int n_key, + const uint32* query, const int n_query); +void search_key_gpu(int* idx, const uint32* key, const int n_key, + const uint32* query, const int n_query); + + +template +void align_forward_gpu(Dtype* top_data, const int top_h, const int channel, + const Dtype* btm_data, const int btm_h, const int* idx); +template +void align_backward_gpu(const Dtype* top_data, const int top_h, const int channel, + Dtype* btm_data, const int btm_h, const int* idx); + + +// TODO: The implementation of align_forward_gpu, octree_pad_gpu, and +// octree_gather_gpu is very similar, try to merge these codes: +// 1. Merge octree_gather_gpu and octree_pad_gpu: +// the height of `idx` is `top_h` in octree_gather_gpu +// 2. Change the implementation of align_forward_gpu to make the height +// of `idx` is `top_h` instead of `btm_h` +template +void octree_gather_gpu(Dtype* top_data, const int top_h, const int channel, + const Dtype* btm_data, const int btm_h, const int* idx); +template +void octree_gatherbk_gpu(const Dtype* top_data, const int top_h, const int channel, + Dtype* btm_data, const int btm_h, const int* idx); + + +void octree_mask_gpu(float* out_data, const float* in_data, const int* label, + int height, int mask, int num); + + +// !!! Caveat: for the following two functions, pt and depth +// must be consistent, i.e pt must be in the range [0, 2^depth]^3 +void compute_key(uint32& key, const uint32* pt, const int depth); +void compute_pt(uint32* pt, const uint32& key, const int depth); + +void xyz2key_cpu(uint32* key, const uint32* xyz, const int num, const int depth); +void xyz2key_gpu(uint32* key, const uint32* xyz, const int num, const int depth); +void key2xyz_cpu(uint32* xyz, const uint32* key, const int num, const int depth); +void key2xyz_gpu(uint32* xyz, const uint32* key, const int num, const int depth); + +void key2idx_cpu(int* idx, const uint32* key, const int num); +void key2idx_gpu(int* idx, const uint32* key, const int num); + +void xyz2coord_cpu(float* pt, const uint32* xyz, const int num, const int channel); +void xyz2coord_gpu(float* pt, const uint32* xyz, const int num, const int channel); +void coord2xyz_cpu(uint32* xyz, const float* pt, const int num, const int channel); +void coord2xyz_gpu(uint32* xyz, const float* pt, const int num, const int channel); + +//int content_flag(string str); +template +void key2xyz(Dtype* xyz, const uint32 key, const int depth); + +#endif // _OCTREE_OCTREE_NN_ diff --git a/octree/octree/octree_parser.cpp b/octree/octree/octree_parser.cpp new file mode 100644 index 0000000..58b8782 --- /dev/null +++ b/octree/octree/octree_parser.cpp @@ -0,0 +1,162 @@ +#include "octree_parser.h" +#include "octree_nn.h" +#include "logs.h" + +#include + +void OctreeParser::set_cpu(const void* ptr) { + const_ptr_ = true; + h_metadata_ = reinterpret_cast(const_cast(ptr)); + info_ = reinterpret_cast(h_metadata_); +} + +void OctreeParser::set_cpu(void* ptr, OctreeInfo* octinfo) { + const_ptr_ = false; + h_metadata_ = reinterpret_cast(ptr); + info_ = reinterpret_cast(ptr); + if (octinfo != nullptr) { // update the OctreeInfo with octinfo + memcpy(info_, octinfo, sizeof(OctreeInfo)); + } +} + +OctreeParser::NodeType OctreeParser::node_type(const int t) const { + NodeType ntype = kInternelNode; + if (t == -1) ntype = kLeaf; + if (t == -2) ntype = kNonEmptyLeaf; + return ntype; +} + +const char* OctreeParser::ptr_cpu(const PropType ptype, const int depth) const { + CHECK(h_metadata_ != nullptr); + const char* p = nullptr; + int dis = info_->ptr_dis(ptype, depth); + if (-1 != dis) { + p = h_metadata_ + dis; + } + return p; +} + +const uint32* OctreeParser::key_cpu(const int depth) const { + return reinterpret_cast(ptr_cpu(OctreeInfo::kKey, depth)); +} + +const int* OctreeParser::children_cpu(const int depth) const { + return reinterpret_cast(ptr_cpu(OctreeInfo::kChild, depth)); +} + +const int* OctreeParser::neighbor_cpu(const int depth) const { + return reinterpret_cast(ptr_cpu(OctreeInfo::kNeigh, depth)); +} + +const float* OctreeParser::feature_cpu(const int depth) const { + return reinterpret_cast(ptr_cpu(OctreeInfo::kFeature, depth)); +} + +const float* OctreeParser::label_cpu(const int depth) const { + return reinterpret_cast(ptr_cpu(OctreeInfo::kLabel, depth)); +} + +const float* OctreeParser::split_cpu(const int depth) const { + return reinterpret_cast(ptr_cpu(OctreeInfo::kSplit, depth)); +} + +char* OctreeParser::mutable_ptr_cpu(const OctreeInfo::PropType ptype, const int depth) { + CHECK(const_ptr_ == false); + return const_cast(ptr_cpu(ptype, depth)); +} + +uint32* OctreeParser::mutable_key_cpu(const int depth) { + return reinterpret_cast(mutable_ptr_cpu(OctreeInfo::kKey, depth)); +} + +int* OctreeParser::mutable_children_cpu(const int depth) { + return reinterpret_cast(mutable_ptr_cpu(OctreeInfo::kChild, depth)); +} + +int* OctreeParser::mutable_neighbor_cpu(const int depth) { + return reinterpret_cast(mutable_ptr_cpu(OctreeInfo::kNeigh, depth)); +} + +float* OctreeParser::mutable_feature_cpu(const int depth) { + return reinterpret_cast(mutable_ptr_cpu(OctreeInfo::kFeature, depth)); +} + +float* OctreeParser::mutable_label_cpu(const int depth) { + return reinterpret_cast(mutable_ptr_cpu(OctreeInfo::kLabel, depth)); +} + +float* OctreeParser::mutable_split_cpu(const int depth) { + return reinterpret_cast(mutable_ptr_cpu(OctreeInfo::kSplit, depth)); +} + + +////////////////////////////////////// +void OctreeParser::node_pos(float* xyz, int id, int depth, float* xyz_base) const { + const uint32* keyi = key_cpu(depth) + id; + key2xyz(xyz, *keyi, depth); + + if (xyz_base != nullptr) { + for (int c = 0; c < 3; ++c) { + xyz_base[c] = xyz[c]; + } + } + + for (int c = 0; c < 3; ++c) { + xyz[c] += 0.5f; + } + if (info_->has_displace()) { + const float kDis = 0.8660254f; // = sqrt(3.0f) / 2.0f + float dis = node_dis(id, depth) * kDis; // !!! Note kDis + if (dis == 0) return; + float n[3] = { 0 }; + node_normal(n, id, depth); + for (int c = 0; c < 3; ++c) { + xyz[c] += dis * n[c]; + } + } +} + +void OctreeParser::node_normal(float* n, int id, int depth) const { + int num = info_->node_num(depth); + const float* feature_d = feature_cpu(depth); + int loc = info_->locations(OctreeInfo::kFeature); + int ch = info_->channel(OctreeInfo::kFeature); + if ((loc == -1 || loc == depth) && ch >= 3) { + for (int c = 0; c < 3; ++c) { n[c] = feature_d[c * num + id]; } + } else { + for (int c = 0; c < 3; ++c) { n[c] = 0; } + } +} + +float OctreeParser::node_dis(int id, int depth) const { + int num = info_->node_num(depth); + const float* feature_d = feature_cpu(depth); + int loc = info_->locations(OctreeInfo::kFeature); + int ch = info_->channel(OctreeInfo::kFeature); + if ((loc == -1 || loc == depth) && ch >= 4) { + return feature_d[3 * num + id]; + } else { + return 0; + } +} + +template +void OctreeParser::key2xyz(Dtype* xyz, const uint32& key, const int depth) const { + if (info_->is_key2xyz()) { + //!!! Caveat: the octree depth should be less than 8 + const unsigned char* pt = reinterpret_cast(&key); + for (int c = 0; c < 3; ++c) { + xyz[c] = static_cast(pt[c]); + } + } + else { + uint32 pt[3]; + compute_pt(pt, key, depth); + for (int c = 0; c < 3; ++c) { + xyz[c] = static_cast(pt[c]); + } + } +} +template void OctreeParser::key2xyz(float* xyz, const unsigned& k, const int d) const; +template void OctreeParser::key2xyz(unsigned* xyz, const unsigned& k, const int d) const; +template void OctreeParser::key2xyz(int* xyz, const unsigned& k, const int d) const; diff --git a/octree/octree/octree_parser.cu b/octree/octree/octree_parser.cu new file mode 100644 index 0000000..c8e4572 --- /dev/null +++ b/octree/octree/octree_parser.cu @@ -0,0 +1,90 @@ +#include "octree_parser.h" +#include "device_alternate.h" + +void OctreeParser::set_gpu(const void* ptr, const void* oct_info) { + const_ptr_ = true; + d_metadata_ = reinterpret_cast(const_cast(ptr)); + + // oct_info is a host pointer + if (oct_info == nullptr) { + info_ = &info_buffer_; + CUDA_CHECK(cudaMemcpy(info_, ptr, sizeof(OctreeInfo), cudaMemcpyDeviceToHost)); + } else { + info_ = reinterpret_cast(const_cast(oct_info)); + } +} + +void OctreeParser::set_gpu(void* ptr, OctreeInfo* octinfo) { + const_ptr_ = false; + d_metadata_ = reinterpret_cast(ptr); + info_ = &info_buffer_; + if (octinfo != nullptr) { // update the OctreeInfo with octinfo + memcpy(info_, octinfo, sizeof(OctreeInfo)); + CUDA_CHECK(cudaMemcpy(d_metadata_, info_, sizeof(OctreeInfo), cudaMemcpyHostToDevice)); + } else { + CUDA_CHECK(cudaMemcpy(info_, d_metadata_, sizeof(OctreeInfo), cudaMemcpyDeviceToHost)); + } +} + +const char* OctreeParser::ptr_gpu(const PropType ptype, const int depth) const { + CHECK(d_metadata_ != nullptr); + const char* p = nullptr; + int dis = info_->ptr_dis(ptype, depth); + if (-1 != dis) { + p = d_metadata_ + dis; + } + return p; +} + +const uintk* OctreeParser::key_gpu(const int depth) const { + return reinterpret_cast(ptr_gpu(OctreeInfo::kKey, depth)); +} + +const int* OctreeParser::children_gpu(const int depth) const { + return reinterpret_cast(ptr_gpu(OctreeInfo::kChild, depth)); +} + +const int* OctreeParser::neighbor_gpu(const int depth) const { + return reinterpret_cast(ptr_gpu(OctreeInfo::kNeigh, depth)); +} + +const float* OctreeParser::feature_gpu(const int depth) const { + return reinterpret_cast(ptr_gpu(OctreeInfo::kFeature, depth)); +} + +const float* OctreeParser::label_gpu(const int depth) const { + return reinterpret_cast(ptr_gpu(OctreeInfo::kLabel, depth)); +} + +const float* OctreeParser::split_gpu(const int depth) const { + return reinterpret_cast(ptr_gpu(OctreeInfo::kSplit, depth)); +} + +char* OctreeParser::mutable_ptr_gpu(const OctreeInfo::PropType ptype, const int depth) { + CHECK(const_ptr_ == false); + return const_cast(ptr_gpu(ptype, depth)); +} + +uintk* OctreeParser::mutable_key_gpu(const int depth) { + return reinterpret_cast(mutable_ptr_gpu(OctreeInfo::kKey, depth)); +} + +int* OctreeParser::mutable_children_gpu(const int depth) { + return reinterpret_cast(mutable_ptr_gpu(OctreeInfo::kChild, depth)); +} + +int* OctreeParser::mutable_neighbor_gpu(const int depth) { + return reinterpret_cast(mutable_ptr_gpu(OctreeInfo::kNeigh, depth)); +} + +float* OctreeParser::mutable_feature_gpu(const int depth) { + return reinterpret_cast(mutable_ptr_gpu(OctreeInfo::kFeature, depth)); +} + +float* OctreeParser::mutable_label_gpu(const int depth) { + return reinterpret_cast(mutable_ptr_gpu(OctreeInfo::kLabel, depth)); +} + +float* OctreeParser::mutable_split_gpu(const int depth) { + return reinterpret_cast(mutable_ptr_gpu(OctreeInfo::kSplit, depth)); +} \ No newline at end of file diff --git a/octree/octree/octree_parser.h b/octree/octree/octree_parser.h new file mode 100644 index 0000000..54c9ed1 --- /dev/null +++ b/octree/octree/octree_parser.h @@ -0,0 +1,85 @@ +#ifndef _OCTREE_OCTREE_PARSER_ +#define _OCTREE_OCTREE_PARSER_ + +#include +#include +#include +#include "octree_info.h" + +using std::vector; +using std::string; + +class OctreeParser { + public: + typedef uint32_t uint32; + typedef uint64_t uint64; + typedef OctreeInfo::PropType PropType; + enum NodeType {kNonEmptyLeaf = -2, kLeaf = -1, kInternelNode = 0 }; + + public: + OctreeParser() : h_metadata_(nullptr), d_metadata_(nullptr), + info_(nullptr), const_ptr_(true), info_buffer_() {} + void set_cpu(const void* ptr); + void set_gpu(const void* ptr, const void* oct_info = nullptr); + void set_cpu(void* ptr, OctreeInfo* octinfo = nullptr); + void set_gpu(void* ptr, OctreeInfo* octinfo = nullptr); + + const OctreeInfo& info() const { return *info_; } + OctreeInfo& mutable_info() { return *info_; } + + NodeType node_type(const int t) const; + bool is_empty() const { return info_ == nullptr; } + + const char* ptr_raw_cpu() const { return h_metadata_; } + const char* ptr_cpu(const PropType ptype, const int depth) const; + const uint32* key_cpu(const int depth) const; + const int* children_cpu(const int depth) const; + const int* neighbor_cpu(const int depth) const; + const float* feature_cpu(const int depth) const; + const float* label_cpu(const int depth) const; + const float* split_cpu(const int depth) const; + + const char* ptr_raw_gpu() const { return d_metadata_; } + const char* ptr_gpu(const PropType ptype, const int depth) const; + const uint32* key_gpu(const int depth) const; + const int* children_gpu(const int depth) const; + const int* neighbor_gpu(const int depth) const; + const float* feature_gpu(const int depth) const; + const float* label_gpu(const int depth) const; + const float* split_gpu(const int depth) const; + + char* mutable_ptr_cpu(const PropType ptype, const int depth); + uint32* mutable_key_cpu(const int depth); + int* mutable_children_cpu(const int depth); + int* mutable_neighbor_cpu(const int depth); + float* mutable_feature_cpu(const int depth); + float* mutable_label_cpu(const int depth); + float* mutable_split_cpu(const int depth); + + char* mutable_ptr_gpu(const PropType ptype, const int depth); + uint32* mutable_key_gpu(const int depth); + int* mutable_children_gpu(const int depth); + int* mutable_neighbor_gpu(const int depth); + float* mutable_feature_gpu(const int depth); + float* mutable_label_gpu(const int depth); + float* mutable_split_gpu(const int depth); + + ////////////////////////////////////// + void node_pos(float* xyz, int id, int depth, float* xyz_base = nullptr) const; + void node_normal(float* n, int id, int depth) const; + float node_dis(int id, int depth) const; + template + void key2xyz(Dtype* xyz, const uint32& key, const int depth) const; + + protected: + // original pointer + char* h_metadata_; + char* d_metadata_; + OctreeInfo* info_; + bool const_ptr_; + + private: + OctreeInfo info_buffer_; +}; + +#endif // _OCTREE_OCTREE_PARSER_ diff --git a/octree/octree/octree_samples.cpp b/octree/octree/octree_samples.cpp new file mode 100644 index 0000000..4e74506 --- /dev/null +++ b/octree/octree/octree_samples.cpp @@ -0,0 +1,2166 @@ +#include "octree_samples.h" + +namespace octree { + +// this octree contains only one point, and the 2nd level is full +static const unsigned char octree_1[] = { + 0x5f, 0x4f, 0x43, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x31, 0x2e, 0x30, 0x5f, + 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, + 0x02, 0000, 0000, 0000, 0x04, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x01, 0000, 0000, 0000, 0x08, 0000, 0000, 0000, 0x40, 0000, 0000, 0000, + 0x08, 0000, 0000, 0000, 0x08, 0000, 0000, 0000, 0x08, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x09, 0000, 0000, 0000, 0x49, 0000, 0000, 0000, 0x51, 0000, 0000, 0000, + 0x59, 0000, 0000, 0000, 0x61, 0000, 0000, 0000, 0x61, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x08, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x01, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x0b, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x03, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0x42, 0000, 0000, 0000, 0x42, 0000, 0000, 0000, 0x42, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xcc, 0x02, 0000, 0000, 0x50, 0x04, 0000, 0000, + 0xd4, 0x05, 0000, 0000, 0xd4, 0x05, 0000, 0000, 0x34, 0x06, 0000, 0000, + 0x34, 0x06, 0000, 0000, 0x34, 0x06, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0x01, 0000, 0000, 0x01, 0x01, 0000, 0x01, 0000, 0x01, + 0000, 0x01, 0x01, 0x01, 0x01, 0000, 0000, 0x01, 0x01, 0000, 0x01, 0x01, + 0x01, 0x01, 0000, 0x01, 0x01, 0x01, 0x01, 0x01, 0000, 0000, 0000, 0x02, + 0000, 0000, 0x01, 0x02, 0000, 0x01, 0000, 0x02, 0000, 0x01, 0x01, 0x02, + 0x01, 0000, 0000, 0x02, 0x01, 0000, 0x01, 0x02, 0x01, 0x01, 0000, 0x02, + 0x01, 0x01, 0x01, 0x02, 0000, 0000, 0x02, 0x02, 0000, 0000, 0x03, 0x02, + 0000, 0x01, 0x02, 0x02, 0000, 0x01, 0x03, 0x02, 0x01, 0000, 0x02, 0x02, + 0x01, 0000, 0x03, 0x02, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x03, 0x02, + 0000, 0x02, 0000, 0x02, 0000, 0x02, 0x01, 0x02, 0000, 0x03, 0000, 0x02, + 0000, 0x03, 0x01, 0x02, 0x01, 0x02, 0000, 0x02, 0x01, 0x02, 0x01, 0x02, + 0x01, 0x03, 0000, 0x02, 0x01, 0x03, 0x01, 0x02, 0000, 0x02, 0x02, 0x02, + 0000, 0x02, 0x03, 0x02, 0000, 0x03, 0x02, 0x02, 0000, 0x03, 0x03, 0x02, + 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x03, 0x02, 0x01, 0x03, 0x02, 0x02, + 0x01, 0x03, 0x03, 0x02, 0x02, 0000, 0000, 0x02, 0x02, 0000, 0x01, 0x02, + 0x02, 0x01, 0000, 0x02, 0x02, 0x01, 0x01, 0x02, 0x03, 0000, 0000, 0x02, + 0x03, 0000, 0x01, 0x02, 0x03, 0x01, 0000, 0x02, 0x03, 0x01, 0x01, 0x02, + 0x02, 0000, 0x02, 0x02, 0x02, 0000, 0x03, 0x02, 0x02, 0x01, 0x02, 0x02, + 0x02, 0x01, 0x03, 0x02, 0x03, 0000, 0x02, 0x02, 0x03, 0000, 0x03, 0x02, + 0x03, 0x01, 0x02, 0x02, 0x03, 0x01, 0x03, 0x02, 0x02, 0x02, 0000, 0x02, + 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0000, 0x02, 0x02, 0x03, 0x01, 0x02, + 0x03, 0x02, 0000, 0x02, 0x03, 0x02, 0x01, 0x02, 0x03, 0x03, 0000, 0x02, + 0x03, 0x03, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, + 0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03, 0x02, 0x03, 0x02, 0x02, 0x02, + 0x03, 0x02, 0x03, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, 0x03, 0x02, + 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x05, 0x04, 0x03, + 0x04, 0x05, 0x05, 0x03, 0x05, 0x04, 0x04, 0x03, 0x05, 0x04, 0x05, 0x03, + 0x05, 0x05, 0x04, 0x03, 0x05, 0x05, 0x05, 0x03, 0x08, 0x08, 0x08, 0x04, + 0x08, 0x08, 0x09, 0x04, 0x08, 0x09, 0x08, 0x04, 0x08, 0x09, 0x09, 0x04, + 0x09, 0x08, 0x08, 0x04, 0x09, 0x08, 0x09, 0x04, 0x09, 0x09, 0x08, 0x04, + 0x09, 0x09, 0x09, 0x04, 0x10, 0x10, 0x10, 0x05, 0x10, 0x10, 0x11, 0x05, + 0x10, 0x11, 0x10, 0x05, 0x10, 0x11, 0x11, 0x05, 0x11, 0x10, 0x10, 0x05, + 0x11, 0x10, 0x11, 0x05, 0x11, 0x11, 0x10, 0x05, 0x11, 0x11, 0x11, 0x05, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x02, 0000, 0000, 0000, 0x03, 0000, 0000, 0000, 0x04, 0000, 0000, 0000, + 0x05, 0000, 0000, 0000, 0x06, 0000, 0000, 0000, 0x07, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x3b, 0xcd, 0x13, 0x3f, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x3b, 0xcd, 0x13, 0x3f, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x3b, 0xcd, 0x13, 0x3f, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x00 +}; + +// this is an normal octree +static const unsigned char octree_2[] = { + 0x5f, 0x4f, 0x43, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x31, 0x2e, 0x30, 0x5f, + 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, + 0x02, 0000, 0000, 0000, 0x04, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x01, 0000, 0000, 0000, 0x08, 0000, 0000, 0000, 0x40, 0000, 0000, 0000, + 0x50, 0000, 0000, 0000, 0x90, 0000, 0000, 0000, 0x38, 0x01, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x09, 0000, 0000, 0000, 0x49, 0000, 0000, 0000, 0x99, 0000, 0000, 0000, + 0x29, 0x01, 0000, 0000, 0x61, 0x02, 0000, 0000, 0x61, 0x02, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x08, 0000, 0000, 0000, 0x0a, 0000, 0000, 0000, 0x12, 0000, 0000, 0000, + 0x27, 0000, 0000, 0000, 0xe2, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x0b, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x03, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0x42, 0000, 0000, 0000, 0x42, 0000, 0000, 0000, 0x42, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xcc, 0x02, 0000, 0000, 0x50, 0x0c, 0000, 0000, + 0xd4, 0x15, 0000, 0000, 0xd4, 0x15, 0000, 0000, 0x74, 0x24, 0000, 0000, + 0x74, 0x24, 0000, 0000, 0x74, 0x24, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0x01, 0000, 0000, 0x01, 0x01, 0000, 0x01, 0000, 0x01, + 0000, 0x01, 0x01, 0x01, 0x01, 0000, 0000, 0x01, 0x01, 0000, 0x01, 0x01, + 0x01, 0x01, 0000, 0x01, 0x01, 0x01, 0x01, 0x01, 0000, 0000, 0000, 0x02, + 0000, 0000, 0x01, 0x02, 0000, 0x01, 0000, 0x02, 0000, 0x01, 0x01, 0x02, + 0x01, 0000, 0000, 0x02, 0x01, 0000, 0x01, 0x02, 0x01, 0x01, 0000, 0x02, + 0x01, 0x01, 0x01, 0x02, 0000, 0000, 0x02, 0x02, 0000, 0000, 0x03, 0x02, + 0000, 0x01, 0x02, 0x02, 0000, 0x01, 0x03, 0x02, 0x01, 0000, 0x02, 0x02, + 0x01, 0000, 0x03, 0x02, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x03, 0x02, + 0000, 0x02, 0000, 0x02, 0000, 0x02, 0x01, 0x02, 0000, 0x03, 0000, 0x02, + 0000, 0x03, 0x01, 0x02, 0x01, 0x02, 0000, 0x02, 0x01, 0x02, 0x01, 0x02, + 0x01, 0x03, 0000, 0x02, 0x01, 0x03, 0x01, 0x02, 0000, 0x02, 0x02, 0x02, + 0000, 0x02, 0x03, 0x02, 0000, 0x03, 0x02, 0x02, 0000, 0x03, 0x03, 0x02, + 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x03, 0x02, 0x01, 0x03, 0x02, 0x02, + 0x01, 0x03, 0x03, 0x02, 0x02, 0000, 0000, 0x02, 0x02, 0000, 0x01, 0x02, + 0x02, 0x01, 0000, 0x02, 0x02, 0x01, 0x01, 0x02, 0x03, 0000, 0000, 0x02, + 0x03, 0000, 0x01, 0x02, 0x03, 0x01, 0000, 0x02, 0x03, 0x01, 0x01, 0x02, + 0x02, 0000, 0x02, 0x02, 0x02, 0000, 0x03, 0x02, 0x02, 0x01, 0x02, 0x02, + 0x02, 0x01, 0x03, 0x02, 0x03, 0000, 0x02, 0x02, 0x03, 0000, 0x03, 0x02, + 0x03, 0x01, 0x02, 0x02, 0x03, 0x01, 0x03, 0x02, 0x02, 0x02, 0000, 0x02, + 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0000, 0x02, 0x02, 0x03, 0x01, 0x02, + 0x03, 0x02, 0000, 0x02, 0x03, 0x02, 0x01, 0x02, 0x03, 0x03, 0000, 0x02, + 0x03, 0x03, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, + 0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03, 0x02, 0x03, 0x02, 0x02, 0x02, + 0x03, 0x02, 0x03, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, 0x03, 0x02, + 0x02, 0000, 0x02, 0x03, 0x02, 0000, 0x03, 0x03, 0x02, 0x01, 0x02, 0x03, + 0x02, 0x01, 0x03, 0x03, 0x03, 0000, 0x02, 0x03, 0x03, 0000, 0x03, 0x03, + 0x03, 0x01, 0x02, 0x03, 0x03, 0x01, 0x03, 0x03, 0x02, 0x02, 0x02, 0x03, + 0x02, 0x02, 0x03, 0x03, 0x02, 0x03, 0x02, 0x03, 0x02, 0x03, 0x03, 0x03, + 0x03, 0x02, 0x02, 0x03, 0x03, 0x02, 0x03, 0x03, 0x03, 0x03, 0x02, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x02, 0000, 0x04, 0x03, 0x02, 0000, 0x05, 0x03, + 0x02, 0x01, 0x04, 0x03, 0x02, 0x01, 0x05, 0x03, 0x03, 0000, 0x04, 0x03, + 0x03, 0000, 0x05, 0x03, 0x03, 0x01, 0x04, 0x03, 0x03, 0x01, 0x05, 0x03, + 0x02, 0x02, 0x04, 0x03, 0x02, 0x02, 0x05, 0x03, 0x02, 0x03, 0x04, 0x03, + 0x02, 0x03, 0x05, 0x03, 0x03, 0x02, 0x04, 0x03, 0x03, 0x02, 0x05, 0x03, + 0x03, 0x03, 0x04, 0x03, 0x03, 0x03, 0x05, 0x03, 0x02, 0x04, 0x02, 0x03, + 0x02, 0x04, 0x03, 0x03, 0x02, 0x05, 0x02, 0x03, 0x02, 0x05, 0x03, 0x03, + 0x03, 0x04, 0x02, 0x03, 0x03, 0x04, 0x03, 0x03, 0x03, 0x05, 0x02, 0x03, + 0x03, 0x05, 0x03, 0x03, 0x02, 0x06, 0x02, 0x03, 0x02, 0x06, 0x03, 0x03, + 0x02, 0x07, 0x02, 0x03, 0x02, 0x07, 0x03, 0x03, 0x03, 0x06, 0x02, 0x03, + 0x03, 0x06, 0x03, 0x03, 0x03, 0x07, 0x02, 0x03, 0x03, 0x07, 0x03, 0x03, + 0x02, 0x04, 0x04, 0x03, 0x02, 0x04, 0x05, 0x03, 0x02, 0x05, 0x04, 0x03, + 0x02, 0x05, 0x05, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x04, 0x05, 0x03, + 0x03, 0x05, 0x04, 0x03, 0x03, 0x05, 0x05, 0x03, 0x02, 0x06, 0x04, 0x03, + 0x02, 0x06, 0x05, 0x03, 0x02, 0x07, 0x04, 0x03, 0x02, 0x07, 0x05, 0x03, + 0x03, 0x06, 0x04, 0x03, 0x03, 0x06, 0x05, 0x03, 0x03, 0x07, 0x04, 0x03, + 0x03, 0x07, 0x05, 0x03, 0x04, 0x06, 0x02, 0x03, 0x04, 0x06, 0x03, 0x03, + 0x04, 0x07, 0x02, 0x03, 0x04, 0x07, 0x03, 0x03, 0x05, 0x06, 0x02, 0x03, + 0x05, 0x06, 0x03, 0x03, 0x05, 0x07, 0x02, 0x03, 0x05, 0x07, 0x03, 0x03, + 0x04, 0x06, 0x04, 0x03, 0x04, 0x06, 0x05, 0x03, 0x04, 0x07, 0x04, 0x03, + 0x04, 0x07, 0x05, 0x03, 0x05, 0x06, 0x04, 0x03, 0x05, 0x06, 0x05, 0x03, + 0x05, 0x07, 0x04, 0x03, 0x05, 0x07, 0x05, 0x03, 0x06, 0000, 0x06, 0x04, + 0x06, 0000, 0x07, 0x04, 0x06, 0x01, 0x06, 0x04, 0x06, 0x01, 0x07, 0x04, + 0x07, 0000, 0x06, 0x04, 0x07, 0000, 0x07, 0x04, 0x07, 0x01, 0x06, 0x04, + 0x07, 0x01, 0x07, 0x04, 0x06, 0x02, 0x06, 0x04, 0x06, 0x02, 0x07, 0x04, + 0x06, 0x03, 0x06, 0x04, 0x06, 0x03, 0x07, 0x04, 0x07, 0x02, 0x06, 0x04, + 0x07, 0x02, 0x07, 0x04, 0x07, 0x03, 0x06, 0x04, 0x07, 0x03, 0x07, 0x04, + 0x06, 0x04, 0x06, 0x04, 0x06, 0x04, 0x07, 0x04, 0x06, 0x05, 0x06, 0x04, + 0x06, 0x05, 0x07, 0x04, 0x07, 0x04, 0x06, 0x04, 0x07, 0x04, 0x07, 0x04, + 0x07, 0x05, 0x06, 0x04, 0x07, 0x05, 0x07, 0x04, 0x06, 0x06, 0x06, 0x04, + 0x06, 0x06, 0x07, 0x04, 0x06, 0x07, 0x06, 0x04, 0x06, 0x07, 0x07, 0x04, + 0x07, 0x06, 0x06, 0x04, 0x07, 0x06, 0x07, 0x04, 0x07, 0x07, 0x06, 0x04, + 0x07, 0x07, 0x07, 0x04, 0x06, 0000, 0x08, 0x04, 0x06, 0000, 0x09, 0x04, + 0x06, 0x01, 0x08, 0x04, 0x06, 0x01, 0x09, 0x04, 0x07, 0000, 0x08, 0x04, + 0x07, 0000, 0x09, 0x04, 0x07, 0x01, 0x08, 0x04, 0x07, 0x01, 0x09, 0x04, + 0x06, 0x02, 0x08, 0x04, 0x06, 0x02, 0x09, 0x04, 0x06, 0x03, 0x08, 0x04, + 0x06, 0x03, 0x09, 0x04, 0x07, 0x02, 0x08, 0x04, 0x07, 0x02, 0x09, 0x04, + 0x07, 0x03, 0x08, 0x04, 0x07, 0x03, 0x09, 0x04, 0x06, 0x04, 0x08, 0x04, + 0x06, 0x04, 0x09, 0x04, 0x06, 0x05, 0x08, 0x04, 0x06, 0x05, 0x09, 0x04, + 0x07, 0x04, 0x08, 0x04, 0x07, 0x04, 0x09, 0x04, 0x07, 0x05, 0x08, 0x04, + 0x07, 0x05, 0x09, 0x04, 0x06, 0x06, 0x08, 0x04, 0x06, 0x06, 0x09, 0x04, + 0x06, 0x07, 0x08, 0x04, 0x06, 0x07, 0x09, 0x04, 0x07, 0x06, 0x08, 0x04, + 0x07, 0x06, 0x09, 0x04, 0x07, 0x07, 0x08, 0x04, 0x07, 0x07, 0x09, 0x04, + 0x06, 0x08, 0x06, 0x04, 0x06, 0x08, 0x07, 0x04, 0x06, 0x09, 0x06, 0x04, + 0x06, 0x09, 0x07, 0x04, 0x07, 0x08, 0x06, 0x04, 0x07, 0x08, 0x07, 0x04, + 0x07, 0x09, 0x06, 0x04, 0x07, 0x09, 0x07, 0x04, 0x06, 0x0a, 0x06, 0x04, + 0x06, 0x0a, 0x07, 0x04, 0x06, 0x0b, 0x06, 0x04, 0x06, 0x0b, 0x07, 0x04, + 0x07, 0x0a, 0x06, 0x04, 0x07, 0x0a, 0x07, 0x04, 0x07, 0x0b, 0x06, 0x04, + 0x07, 0x0b, 0x07, 0x04, 0x06, 0x0c, 0x06, 0x04, 0x06, 0x0c, 0x07, 0x04, + 0x06, 0x0d, 0x06, 0x04, 0x06, 0x0d, 0x07, 0x04, 0x07, 0x0c, 0x06, 0x04, + 0x07, 0x0c, 0x07, 0x04, 0x07, 0x0d, 0x06, 0x04, 0x07, 0x0d, 0x07, 0x04, + 0x06, 0x0e, 0x06, 0x04, 0x06, 0x0e, 0x07, 0x04, 0x06, 0x0f, 0x06, 0x04, + 0x06, 0x0f, 0x07, 0x04, 0x07, 0x0e, 0x06, 0x04, 0x07, 0x0e, 0x07, 0x04, + 0x07, 0x0f, 0x06, 0x04, 0x07, 0x0f, 0x07, 0x04, 0x06, 0x08, 0x08, 0x04, + 0x06, 0x08, 0x09, 0x04, 0x06, 0x09, 0x08, 0x04, 0x06, 0x09, 0x09, 0x04, + 0x07, 0x08, 0x08, 0x04, 0x07, 0x08, 0x09, 0x04, 0x07, 0x09, 0x08, 0x04, + 0x07, 0x09, 0x09, 0x04, 0x06, 0x0a, 0x08, 0x04, 0x06, 0x0a, 0x09, 0x04, + 0x06, 0x0b, 0x08, 0x04, 0x06, 0x0b, 0x09, 0x04, 0x07, 0x0a, 0x08, 0x04, + 0x07, 0x0a, 0x09, 0x04, 0x07, 0x0b, 0x08, 0x04, 0x07, 0x0b, 0x09, 0x04, + 0x06, 0x0c, 0x08, 0x04, 0x06, 0x0c, 0x09, 0x04, 0x06, 0x0d, 0x08, 0x04, + 0x06, 0x0d, 0x09, 0x04, 0x07, 0x0c, 0x08, 0x04, 0x07, 0x0c, 0x09, 0x04, + 0x07, 0x0d, 0x08, 0x04, 0x07, 0x0d, 0x09, 0x04, 0x06, 0x0e, 0x08, 0x04, + 0x06, 0x0e, 0x09, 0x04, 0x06, 0x0f, 0x08, 0x04, 0x06, 0x0f, 0x09, 0x04, + 0x07, 0x0e, 0x08, 0x04, 0x07, 0x0e, 0x09, 0x04, 0x07, 0x0f, 0x08, 0x04, + 0x07, 0x0f, 0x09, 0x04, 0x08, 0x0e, 0x06, 0x04, 0x08, 0x0e, 0x07, 0x04, + 0x08, 0x0f, 0x06, 0x04, 0x08, 0x0f, 0x07, 0x04, 0x09, 0x0e, 0x06, 0x04, + 0x09, 0x0e, 0x07, 0x04, 0x09, 0x0f, 0x06, 0x04, 0x09, 0x0f, 0x07, 0x04, + 0x08, 0x0e, 0x08, 0x04, 0x08, 0x0e, 0x09, 0x04, 0x08, 0x0f, 0x08, 0x04, + 0x08, 0x0f, 0x09, 0x04, 0x09, 0x0e, 0x08, 0x04, 0x09, 0x0e, 0x09, 0x04, + 0x09, 0x0f, 0x08, 0x04, 0x09, 0x0f, 0x09, 0x04, 0x0e, 0000, 0x0e, 0x05, + 0x0e, 0000, 0x0f, 0x05, 0x0e, 0x01, 0x0e, 0x05, 0x0e, 0x01, 0x0f, 0x05, + 0x0f, 0000, 0x0e, 0x05, 0x0f, 0000, 0x0f, 0x05, 0x0f, 0x01, 0x0e, 0x05, + 0x0f, 0x01, 0x0f, 0x05, 0x0e, 0x02, 0x0e, 0x05, 0x0e, 0x02, 0x0f, 0x05, + 0x0e, 0x03, 0x0e, 0x05, 0x0e, 0x03, 0x0f, 0x05, 0x0f, 0x02, 0x0e, 0x05, + 0x0f, 0x02, 0x0f, 0x05, 0x0f, 0x03, 0x0e, 0x05, 0x0f, 0x03, 0x0f, 0x05, + 0x0e, 0x04, 0x0e, 0x05, 0x0e, 0x04, 0x0f, 0x05, 0x0e, 0x05, 0x0e, 0x05, + 0x0e, 0x05, 0x0f, 0x05, 0x0f, 0x04, 0x0e, 0x05, 0x0f, 0x04, 0x0f, 0x05, + 0x0f, 0x05, 0x0e, 0x05, 0x0f, 0x05, 0x0f, 0x05, 0x0e, 0x06, 0x0e, 0x05, + 0x0e, 0x06, 0x0f, 0x05, 0x0e, 0x07, 0x0e, 0x05, 0x0e, 0x07, 0x0f, 0x05, + 0x0f, 0x06, 0x0e, 0x05, 0x0f, 0x06, 0x0f, 0x05, 0x0f, 0x07, 0x0e, 0x05, + 0x0f, 0x07, 0x0f, 0x05, 0x0e, 0x08, 0x0e, 0x05, 0x0e, 0x08, 0x0f, 0x05, + 0x0e, 0x09, 0x0e, 0x05, 0x0e, 0x09, 0x0f, 0x05, 0x0f, 0x08, 0x0e, 0x05, + 0x0f, 0x08, 0x0f, 0x05, 0x0f, 0x09, 0x0e, 0x05, 0x0f, 0x09, 0x0f, 0x05, + 0x0e, 0x0a, 0x0e, 0x05, 0x0e, 0x0a, 0x0f, 0x05, 0x0e, 0x0b, 0x0e, 0x05, + 0x0e, 0x0b, 0x0f, 0x05, 0x0f, 0x0a, 0x0e, 0x05, 0x0f, 0x0a, 0x0f, 0x05, + 0x0f, 0x0b, 0x0e, 0x05, 0x0f, 0x0b, 0x0f, 0x05, 0x0e, 0x0c, 0x0e, 0x05, + 0x0e, 0x0c, 0x0f, 0x05, 0x0e, 0x0d, 0x0e, 0x05, 0x0e, 0x0d, 0x0f, 0x05, + 0x0f, 0x0c, 0x0e, 0x05, 0x0f, 0x0c, 0x0f, 0x05, 0x0f, 0x0d, 0x0e, 0x05, + 0x0f, 0x0d, 0x0f, 0x05, 0x0e, 0x0e, 0x0e, 0x05, 0x0e, 0x0e, 0x0f, 0x05, + 0x0e, 0x0f, 0x0e, 0x05, 0x0e, 0x0f, 0x0f, 0x05, 0x0f, 0x0e, 0x0e, 0x05, + 0x0f, 0x0e, 0x0f, 0x05, 0x0f, 0x0f, 0x0e, 0x05, 0x0f, 0x0f, 0x0f, 0x05, + 0x0e, 0000, 0x10, 0x05, 0x0e, 0000, 0x11, 0x05, 0x0e, 0x01, 0x10, 0x05, + 0x0e, 0x01, 0x11, 0x05, 0x0f, 0000, 0x10, 0x05, 0x0f, 0000, 0x11, 0x05, + 0x0f, 0x01, 0x10, 0x05, 0x0f, 0x01, 0x11, 0x05, 0x0e, 0x02, 0x10, 0x05, + 0x0e, 0x02, 0x11, 0x05, 0x0e, 0x03, 0x10, 0x05, 0x0e, 0x03, 0x11, 0x05, + 0x0f, 0x02, 0x10, 0x05, 0x0f, 0x02, 0x11, 0x05, 0x0f, 0x03, 0x10, 0x05, + 0x0f, 0x03, 0x11, 0x05, 0x0e, 0x04, 0x10, 0x05, 0x0e, 0x04, 0x11, 0x05, + 0x0e, 0x05, 0x10, 0x05, 0x0e, 0x05, 0x11, 0x05, 0x0f, 0x04, 0x10, 0x05, + 0x0f, 0x04, 0x11, 0x05, 0x0f, 0x05, 0x10, 0x05, 0x0f, 0x05, 0x11, 0x05, + 0x0e, 0x06, 0x10, 0x05, 0x0e, 0x06, 0x11, 0x05, 0x0e, 0x07, 0x10, 0x05, + 0x0e, 0x07, 0x11, 0x05, 0x0f, 0x06, 0x10, 0x05, 0x0f, 0x06, 0x11, 0x05, + 0x0f, 0x07, 0x10, 0x05, 0x0f, 0x07, 0x11, 0x05, 0x0e, 0x08, 0x10, 0x05, + 0x0e, 0x08, 0x11, 0x05, 0x0e, 0x09, 0x10, 0x05, 0x0e, 0x09, 0x11, 0x05, + 0x0f, 0x08, 0x10, 0x05, 0x0f, 0x08, 0x11, 0x05, 0x0f, 0x09, 0x10, 0x05, + 0x0f, 0x09, 0x11, 0x05, 0x0e, 0x0a, 0x10, 0x05, 0x0e, 0x0a, 0x11, 0x05, + 0x0e, 0x0b, 0x10, 0x05, 0x0e, 0x0b, 0x11, 0x05, 0x0f, 0x0a, 0x10, 0x05, + 0x0f, 0x0a, 0x11, 0x05, 0x0f, 0x0b, 0x10, 0x05, 0x0f, 0x0b, 0x11, 0x05, + 0x0e, 0x0c, 0x10, 0x05, 0x0e, 0x0c, 0x11, 0x05, 0x0e, 0x0d, 0x10, 0x05, + 0x0e, 0x0d, 0x11, 0x05, 0x0f, 0x0c, 0x10, 0x05, 0x0f, 0x0c, 0x11, 0x05, + 0x0f, 0x0d, 0x10, 0x05, 0x0f, 0x0d, 0x11, 0x05, 0x0e, 0x0e, 0x10, 0x05, + 0x0e, 0x0e, 0x11, 0x05, 0x0e, 0x0f, 0x10, 0x05, 0x0e, 0x0f, 0x11, 0x05, + 0x0f, 0x0e, 0x10, 0x05, 0x0f, 0x0e, 0x11, 0x05, 0x0f, 0x0f, 0x10, 0x05, + 0x0f, 0x0f, 0x11, 0x05, 0x0e, 0x10, 0x0e, 0x05, 0x0e, 0x10, 0x0f, 0x05, + 0x0e, 0x11, 0x0e, 0x05, 0x0e, 0x11, 0x0f, 0x05, 0x0f, 0x10, 0x0e, 0x05, + 0x0f, 0x10, 0x0f, 0x05, 0x0f, 0x11, 0x0e, 0x05, 0x0f, 0x11, 0x0f, 0x05, + 0x0e, 0x12, 0x0e, 0x05, 0x0e, 0x12, 0x0f, 0x05, 0x0e, 0x13, 0x0e, 0x05, + 0x0e, 0x13, 0x0f, 0x05, 0x0f, 0x12, 0x0e, 0x05, 0x0f, 0x12, 0x0f, 0x05, + 0x0f, 0x13, 0x0e, 0x05, 0x0f, 0x13, 0x0f, 0x05, 0x0e, 0x14, 0x0e, 0x05, + 0x0e, 0x14, 0x0f, 0x05, 0x0e, 0x15, 0x0e, 0x05, 0x0e, 0x15, 0x0f, 0x05, + 0x0f, 0x14, 0x0e, 0x05, 0x0f, 0x14, 0x0f, 0x05, 0x0f, 0x15, 0x0e, 0x05, + 0x0f, 0x15, 0x0f, 0x05, 0x0e, 0x16, 0x0e, 0x05, 0x0e, 0x16, 0x0f, 0x05, + 0x0e, 0x17, 0x0e, 0x05, 0x0e, 0x17, 0x0f, 0x05, 0x0f, 0x16, 0x0e, 0x05, + 0x0f, 0x16, 0x0f, 0x05, 0x0f, 0x17, 0x0e, 0x05, 0x0f, 0x17, 0x0f, 0x05, + 0x0e, 0x18, 0x0e, 0x05, 0x0e, 0x18, 0x0f, 0x05, 0x0e, 0x19, 0x0e, 0x05, + 0x0e, 0x19, 0x0f, 0x05, 0x0f, 0x18, 0x0e, 0x05, 0x0f, 0x18, 0x0f, 0x05, + 0x0f, 0x19, 0x0e, 0x05, 0x0f, 0x19, 0x0f, 0x05, 0x0e, 0x1a, 0x0e, 0x05, + 0x0e, 0x1a, 0x0f, 0x05, 0x0e, 0x1b, 0x0e, 0x05, 0x0e, 0x1b, 0x0f, 0x05, + 0x0f, 0x1a, 0x0e, 0x05, 0x0f, 0x1a, 0x0f, 0x05, 0x0f, 0x1b, 0x0e, 0x05, + 0x0f, 0x1b, 0x0f, 0x05, 0x0e, 0x1c, 0x0e, 0x05, 0x0e, 0x1c, 0x0f, 0x05, + 0x0e, 0x1d, 0x0e, 0x05, 0x0e, 0x1d, 0x0f, 0x05, 0x0f, 0x1c, 0x0e, 0x05, + 0x0f, 0x1c, 0x0f, 0x05, 0x0f, 0x1d, 0x0e, 0x05, 0x0f, 0x1d, 0x0f, 0x05, + 0x0e, 0x1e, 0x0c, 0x05, 0x0e, 0x1e, 0x0d, 0x05, 0x0e, 0x1f, 0x0c, 0x05, + 0x0e, 0x1f, 0x0d, 0x05, 0x0f, 0x1e, 0x0c, 0x05, 0x0f, 0x1e, 0x0d, 0x05, + 0x0f, 0x1f, 0x0c, 0x05, 0x0f, 0x1f, 0x0d, 0x05, 0x0e, 0x1e, 0x0e, 0x05, + 0x0e, 0x1e, 0x0f, 0x05, 0x0e, 0x1f, 0x0e, 0x05, 0x0e, 0x1f, 0x0f, 0x05, + 0x0f, 0x1e, 0x0e, 0x05, 0x0f, 0x1e, 0x0f, 0x05, 0x0f, 0x1f, 0x0e, 0x05, + 0x0f, 0x1f, 0x0f, 0x05, 0x0e, 0x10, 0x10, 0x05, 0x0e, 0x10, 0x11, 0x05, + 0x0e, 0x11, 0x10, 0x05, 0x0e, 0x11, 0x11, 0x05, 0x0f, 0x10, 0x10, 0x05, + 0x0f, 0x10, 0x11, 0x05, 0x0f, 0x11, 0x10, 0x05, 0x0f, 0x11, 0x11, 0x05, + 0x0e, 0x12, 0x10, 0x05, 0x0e, 0x12, 0x11, 0x05, 0x0e, 0x13, 0x10, 0x05, + 0x0e, 0x13, 0x11, 0x05, 0x0f, 0x12, 0x10, 0x05, 0x0f, 0x12, 0x11, 0x05, + 0x0f, 0x13, 0x10, 0x05, 0x0f, 0x13, 0x11, 0x05, 0x0e, 0x14, 0x10, 0x05, + 0x0e, 0x14, 0x11, 0x05, 0x0e, 0x15, 0x10, 0x05, 0x0e, 0x15, 0x11, 0x05, + 0x0f, 0x14, 0x10, 0x05, 0x0f, 0x14, 0x11, 0x05, 0x0f, 0x15, 0x10, 0x05, + 0x0f, 0x15, 0x11, 0x05, 0x0e, 0x16, 0x10, 0x05, 0x0e, 0x16, 0x11, 0x05, + 0x0e, 0x17, 0x10, 0x05, 0x0e, 0x17, 0x11, 0x05, 0x0f, 0x16, 0x10, 0x05, + 0x0f, 0x16, 0x11, 0x05, 0x0f, 0x17, 0x10, 0x05, 0x0f, 0x17, 0x11, 0x05, + 0x0e, 0x18, 0x10, 0x05, 0x0e, 0x18, 0x11, 0x05, 0x0e, 0x19, 0x10, 0x05, + 0x0e, 0x19, 0x11, 0x05, 0x0f, 0x18, 0x10, 0x05, 0x0f, 0x18, 0x11, 0x05, + 0x0f, 0x19, 0x10, 0x05, 0x0f, 0x19, 0x11, 0x05, 0x0e, 0x1a, 0x10, 0x05, + 0x0e, 0x1a, 0x11, 0x05, 0x0e, 0x1b, 0x10, 0x05, 0x0e, 0x1b, 0x11, 0x05, + 0x0f, 0x1a, 0x10, 0x05, 0x0f, 0x1a, 0x11, 0x05, 0x0f, 0x1b, 0x10, 0x05, + 0x0f, 0x1b, 0x11, 0x05, 0x0e, 0x1c, 0x10, 0x05, 0x0e, 0x1c, 0x11, 0x05, + 0x0e, 0x1d, 0x10, 0x05, 0x0e, 0x1d, 0x11, 0x05, 0x0f, 0x1c, 0x10, 0x05, + 0x0f, 0x1c, 0x11, 0x05, 0x0f, 0x1d, 0x10, 0x05, 0x0f, 0x1d, 0x11, 0x05, + 0x0e, 0x1e, 0x10, 0x05, 0x0e, 0x1e, 0x11, 0x05, 0x0e, 0x1f, 0x10, 0x05, + 0x0e, 0x1f, 0x11, 0x05, 0x0f, 0x1e, 0x10, 0x05, 0x0f, 0x1e, 0x11, 0x05, + 0x0f, 0x1f, 0x10, 0x05, 0x0f, 0x1f, 0x11, 0x05, 0x10, 0x1e, 0x0c, 0x05, + 0x10, 0x1e, 0x0d, 0x05, 0x10, 0x1f, 0x0c, 0x05, 0x10, 0x1f, 0x0d, 0x05, + 0x11, 0x1e, 0x0c, 0x05, 0x11, 0x1e, 0x0d, 0x05, 0x11, 0x1f, 0x0c, 0x05, + 0x11, 0x1f, 0x0d, 0x05, 0x10, 0x1e, 0x0e, 0x05, 0x10, 0x1e, 0x0f, 0x05, + 0x10, 0x1f, 0x0e, 0x05, 0x10, 0x1f, 0x0f, 0x05, 0x11, 0x1e, 0x0e, 0x05, + 0x11, 0x1e, 0x0f, 0x05, 0x11, 0x1f, 0x0e, 0x05, 0x11, 0x1f, 0x0f, 0x05, + 0x12, 0x1e, 0x0c, 0x05, 0x12, 0x1e, 0x0d, 0x05, 0x12, 0x1f, 0x0c, 0x05, + 0x12, 0x1f, 0x0d, 0x05, 0x13, 0x1e, 0x0c, 0x05, 0x13, 0x1e, 0x0d, 0x05, + 0x13, 0x1f, 0x0c, 0x05, 0x13, 0x1f, 0x0d, 0x05, 0x12, 0x1e, 0x0e, 0x05, + 0x12, 0x1e, 0x0f, 0x05, 0x12, 0x1f, 0x0e, 0x05, 0x12, 0x1f, 0x0f, 0x05, + 0x13, 0x1e, 0x0e, 0x05, 0x13, 0x1e, 0x0f, 0x05, 0x13, 0x1f, 0x0e, 0x05, + 0x13, 0x1f, 0x0f, 0x05, 0x10, 0x1e, 0x10, 0x05, 0x10, 0x1e, 0x11, 0x05, + 0x10, 0x1f, 0x10, 0x05, 0x10, 0x1f, 0x11, 0x05, 0x11, 0x1e, 0x10, 0x05, + 0x11, 0x1e, 0x11, 0x05, 0x11, 0x1f, 0x10, 0x05, 0x11, 0x1f, 0x11, 0x05, + 0x12, 0x1e, 0x10, 0x05, 0x12, 0x1e, 0x11, 0x05, 0x12, 0x1f, 0x10, 0x05, + 0x12, 0x1f, 0x11, 0x05, 0x13, 0x1e, 0x10, 0x05, 0x13, 0x1e, 0x11, 0x05, + 0x13, 0x1f, 0x10, 0x05, 0x13, 0x1f, 0x11, 0x05, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x02, 0000, 0000, 0000, + 0x03, 0000, 0000, 0000, 0x04, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, + 0x06, 0000, 0000, 0000, 0x07, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x01, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x03, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x05, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x06, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x07, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x08, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x09, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x01, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x02, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x03, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x05, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x06, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x07, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x08, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x09, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0a, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x0b, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x0c, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x0d, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0e, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x0f, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x10, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x11, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x01, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x02, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x03, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x05, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x06, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x07, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x08, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x09, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x0a, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x0b, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0c, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x0d, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0e, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x0f, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x10, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x11, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x12, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x13, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x14, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x15, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0000, 0000, 0000, + 0x17, 0000, 0000, 0000, 0x18, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x19, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x1a, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x1c, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x1d, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x1e, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x1f, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x20, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x21, 0000, 0000, 0000, 0x22, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x23, 0000, 0000, 0000, 0x24, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x25, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x26, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x01, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x02, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x03, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x04, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x05, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x06, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x07, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x08, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x09, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x0a, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x0b, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x0c, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x0d, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x0e, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x10, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x11, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x12, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x13, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x14, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x15, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x16, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x17, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x18, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x19, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x1a, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x1b, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x1c, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x1d, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x1e, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x1f, 0000, 0000, 0000, + 0x20, 0000, 0000, 0000, 0x21, 0000, 0000, 0000, 0x22, 0000, 0000, 0000, + 0x23, 0000, 0000, 0000, 0x24, 0000, 0000, 0000, 0x25, 0000, 0000, 0000, + 0x26, 0000, 0000, 0000, 0x27, 0000, 0000, 0000, 0x28, 0000, 0000, 0000, + 0x29, 0000, 0000, 0000, 0x2a, 0000, 0000, 0000, 0x2b, 0000, 0000, 0000, + 0x2c, 0000, 0000, 0000, 0x2d, 0000, 0000, 0000, 0x2e, 0000, 0000, 0000, + 0x2f, 0000, 0000, 0000, 0x30, 0000, 0000, 0000, 0x31, 0000, 0000, 0000, + 0x32, 0000, 0000, 0000, 0x33, 0000, 0000, 0000, 0x34, 0000, 0000, 0000, + 0x35, 0000, 0000, 0000, 0x36, 0000, 0000, 0000, 0x37, 0000, 0000, 0000, + 0x38, 0000, 0000, 0000, 0x39, 0000, 0000, 0000, 0x3a, 0000, 0000, 0000, + 0x3b, 0000, 0000, 0000, 0x3c, 0000, 0000, 0000, 0x3d, 0000, 0000, 0000, + 0x3e, 0000, 0000, 0000, 0x3f, 0000, 0000, 0000, 0x40, 0000, 0000, 0000, + 0x41, 0000, 0000, 0000, 0x42, 0000, 0000, 0000, 0x43, 0000, 0000, 0000, + 0x44, 0000, 0000, 0000, 0x45, 0000, 0000, 0000, 0x46, 0000, 0000, 0000, + 0x47, 0000, 0000, 0000, 0x48, 0000, 0000, 0000, 0x49, 0000, 0000, 0000, + 0x4a, 0000, 0000, 0000, 0x4b, 0000, 0000, 0000, 0x4c, 0000, 0000, 0000, + 0x4d, 0000, 0000, 0000, 0x4e, 0000, 0000, 0000, 0x4f, 0000, 0000, 0000, + 0x50, 0000, 0000, 0000, 0x51, 0000, 0000, 0000, 0x52, 0000, 0000, 0000, + 0x53, 0000, 0000, 0000, 0x54, 0000, 0000, 0000, 0x55, 0000, 0000, 0000, + 0x56, 0000, 0000, 0000, 0x57, 0000, 0000, 0000, 0x58, 0000, 0000, 0000, + 0x59, 0000, 0000, 0000, 0x5a, 0000, 0000, 0000, 0x5b, 0000, 0000, 0000, + 0x5c, 0000, 0000, 0000, 0x5d, 0000, 0000, 0000, 0x5e, 0000, 0000, 0000, + 0x5f, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x60, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x61, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x62, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x63, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x64, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x65, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x66, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x67, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x68, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x69, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x6a, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x6b, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x6c, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x6d, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x6e, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x6f, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x70, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x71, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x72, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x73, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x74, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x75, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x76, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x77, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x78, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x79, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x7a, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x7b, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7c, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x7d, 0000, 0000, 0000, 0x7e, 0000, 0000, 0000, + 0x7f, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x80, 0000, 0000, 0000, 0x81, 0000, 0000, 0000, 0x82, 0000, 0000, 0000, + 0x83, 0000, 0000, 0000, 0x84, 0000, 0000, 0000, 0x85, 0000, 0000, 0000, + 0x86, 0000, 0000, 0000, 0x87, 0000, 0000, 0000, 0x88, 0000, 0000, 0000, + 0x89, 0000, 0000, 0000, 0x8a, 0000, 0000, 0000, 0x8b, 0000, 0000, 0000, + 0x8c, 0000, 0000, 0000, 0x8d, 0000, 0000, 0000, 0x8e, 0000, 0000, 0000, + 0x8f, 0000, 0000, 0000, 0x90, 0000, 0000, 0000, 0x91, 0000, 0000, 0000, + 0x92, 0000, 0000, 0000, 0x93, 0000, 0000, 0000, 0x94, 0000, 0000, 0000, + 0x95, 0000, 0000, 0000, 0x96, 0000, 0000, 0000, 0x97, 0000, 0000, 0000, + 0x98, 0000, 0000, 0000, 0x99, 0000, 0000, 0000, 0x9a, 0000, 0000, 0000, + 0x9b, 0000, 0000, 0000, 0x9c, 0000, 0000, 0000, 0x9d, 0000, 0000, 0000, + 0x9e, 0000, 0000, 0000, 0x9f, 0000, 0000, 0000, 0xa0, 0000, 0000, 0000, + 0xa1, 0000, 0000, 0000, 0xa2, 0000, 0000, 0000, 0xa3, 0000, 0000, 0000, + 0xa4, 0000, 0000, 0000, 0xa5, 0000, 0000, 0000, 0xa6, 0000, 0000, 0000, + 0xa7, 0000, 0000, 0000, 0xa8, 0000, 0000, 0000, 0xa9, 0000, 0000, 0000, + 0xaa, 0000, 0000, 0000, 0xab, 0000, 0000, 0000, 0xac, 0000, 0000, 0000, + 0xad, 0000, 0000, 0000, 0xae, 0000, 0000, 0000, 0xaf, 0000, 0000, 0000, + 0xb0, 0000, 0000, 0000, 0xb1, 0000, 0000, 0000, 0xb2, 0000, 0000, 0000, + 0xb3, 0000, 0000, 0000, 0xb4, 0000, 0000, 0000, 0xb5, 0000, 0000, 0000, + 0xb6, 0000, 0000, 0000, 0xb7, 0000, 0000, 0000, 0xb8, 0000, 0000, 0000, + 0xb9, 0000, 0000, 0000, 0xba, 0000, 0000, 0000, 0xbb, 0000, 0000, 0000, + 0xbc, 0000, 0000, 0000, 0xbd, 0000, 0000, 0000, 0xbe, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xbf, 0000, 0000, 0000, 0xc0, 0000, 0000, 0000, + 0xc1, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc2, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xc3, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xc4, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xc5, 0000, 0000, 0000, 0xc6, 0000, 0000, 0000, 0xc7, 0000, 0000, 0000, + 0xc8, 0000, 0000, 0000, 0xc9, 0000, 0000, 0000, 0xca, 0000, 0000, 0000, + 0xcb, 0000, 0000, 0000, 0xcc, 0000, 0000, 0000, 0xcd, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xce, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xcf, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd0, 0000, 0000, 0000, + 0xd1, 0000, 0000, 0000, 0xd2, 0000, 0000, 0000, 0xd3, 0000, 0000, 0000, + 0xd4, 0000, 0000, 0000, 0xd5, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xd6, 0000, 0000, 0000, 0xd7, 0000, 0000, 0000, + 0xd8, 0000, 0000, 0000, 0xd9, 0000, 0000, 0000, 0xda, 0000, 0000, 0000, + 0xdb, 0000, 0000, 0000, 0xdc, 0000, 0000, 0000, 0xdd, 0000, 0000, 0000, + 0xde, 0000, 0000, 0000, 0xdf, 0000, 0000, 0000, 0xe0, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xe1, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, + 0x2c, 0x99, 0x04, 0xbf, 0000, 0000, 0000, 0000, 0x6d, 0x3f, 0x01, 0xbf, + 0000, 0000, 0000, 0000, 0xab, 0xe2, 0xd1, 0x3e, 0000, 0000, 0000, 0000, + 0x22, 0xc4, 0xbe, 0x3e, 0000, 0000, 0000, 0000, 0x80, 0x8f, 0xf3, 0xbe, + 0000, 0000, 0000, 0000, 0x67, 0x90, 0xfe, 0xbe, 0000, 0000, 0000, 0000, + 0xe1, 0x2a, 0xc5, 0x3e, 0000, 0000, 0000, 0000, 0x7a, 0x79, 0xb8, 0x3e, + 0000, 0000, 0000, 0000, 0xe9, 0x38, 0x01, 0xbf, 0000, 0000, 0000, 0000, + 0x2c, 0xf0, 0000, 0xbf, 0000, 0000, 0000, 0000, 0xf3, 0xfc, 0xc5, 0x3e, + 0000, 0000, 0000, 0000, 0x01, 0x0e, 0xca, 0x3e, 0000, 0000, 0000, 0000, + 0x3c, 0xb8, 0x05, 0xbf, 0000, 0000, 0000, 0000, 0x75, 0x88, 0x04, 0xbf, + 0000, 0000, 0000, 0000, 0x50, 0x58, 0xc5, 0x3e, 0000, 0000, 0000, 0000, + 0xa9, 0x8f, 0xc7, 0x3e, 0000, 0000, 0000, 0000, 0x66, 0xc6, 0x09, 0xbf, + 0000, 0000, 0000, 0000, 0xcc, 0x59, 0x05, 0xbf, 0000, 0000, 0000, 0000, + 0x1b, 0xe1, 0xcc, 0x3e, 0000, 0000, 0000, 0000, 0xdc, 0xe2, 0xcd, 0x3e, + 0000, 0000, 0000, 0000, 0x32, 0x5b, 0x06, 0xbf, 0000, 0000, 0000, 0000, + 0x73, 0x50, 0000, 0xbf, 0000, 0000, 0000, 0000, 0xcb, 0xc2, 0xc9, 0x3e, + 0000, 0000, 0000, 0000, 0xf5, 0x35, 0xae, 0x3e, 0000, 0000, 0000, 0000, + 0xbd, 0x9b, 0x07, 0xbf, 0000, 0000, 0000, 0000, 0x49, 0xf3, 0x06, 0xbf, + 0000, 0000, 0000, 0000, 0x0c, 0x9f, 0x84, 0x3e, 0000, 0000, 0000, 0000, + 0xa9, 0x56, 0x93, 0x3e, 0000, 0000, 0000, 0000, 0xcc, 0x91, 0x07, 0xbf, + 0000, 0000, 0000, 0000, 0xbd, 0x28, 0000, 0xbf, 0000, 0000, 0000, 0000, + 0xfa, 0x59, 0x8b, 0x3e, 0000, 0000, 0000, 0000, 0xd9, 0xe5, 0x8a, 0x3e, + 0x0d, 0x4a, 0xff, 0xbe, 0x8f, 0x17, 0xfd, 0xbe, 0x92, 0xec, 0x7f, 0xbf, + 0x93, 0xc5, 0x01, 0xbf, 0xa4, 0x72, 0x45, 0x3e, 0x5f, 0xf2, 0xe2, 0x3e, + 0xf4, 0xfd, 0x7f, 0x3f, 0x66, 0x51, 0xbf, 0x3e, 0xf2, 0xfd, 0x7f, 0xbf, + 0x3c, 0x05, 0000, 0xbf, 0x77, 0xfe, 0x7f, 0xbf, 0xfe, 0x75, 0x03, 0xbf, + 0xfc, 0xfb, 0x7f, 0x3f, 0xc7, 0xed, 0xbe, 0x3e, 0x43, 0xf9, 0x7f, 0x3f, + 0xa2, 0xb9, 0xc8, 0x3e, 0x43, 0xfb, 0x7f, 0xbf, 0x43, 0x40, 0x04, 0xbf, + 0x3c, 0xe2, 0x7f, 0xbf, 0x37, 0x64, 0x07, 0xbf, 0x69, 0xfa, 0x7f, 0x3f, + 0x40, 0x79, 0xc5, 0x3e, 0x33, 0xeb, 0x7f, 0x3f, 0xf0, 0x1b, 0xc0, 0x3e, + 0x3c, 0xfe, 0x7f, 0xbf, 0x40, 0xd6, 0x06, 0xbf, 0xc2, 0xe0, 0x7f, 0xbf, + 0x42, 0xde, 0xf3, 0xbe, 0x95, 0xf8, 0x7f, 0x3f, 0x71, 0xf0, 0xb8, 0x3e, + 0x3f, 0xf7, 0x7f, 0x3f, 0x8c, 0x2d, 0xcc, 0x3e, 0x33, 0xa9, 0x7f, 0xbf, + 0x7b, 0x37, 0xf3, 0xbe, 0x77, 0xe3, 0x7f, 0xbf, 0xbb, 0x94, 0xbc, 0xbe, + 0xa5, 0xfe, 0x7f, 0x3f, 0x5f, 0xab, 0xd9, 0x3e, 0xe3, 0xf8, 0x7f, 0x3f, + 0x84, 0xe5, 0xd2, 0x3e, 0x6e, 0xed, 0x7f, 0xbf, 0x7c, 0xab, 0xca, 0xbe, + 0xe3, 0xd2, 0x7f, 0xbf, 0x80, 0x46, 0xdc, 0xbe, 0x59, 0xb9, 0x7f, 0x3f, + 0x96, 0xb7, 0xda, 0x3e, 0xa7, 0x53, 0x7f, 0x3f, 0xd6, 0x8f, 0xc0, 0x3e, + 0xd6, 0x7c, 0x7f, 0xbf, 0x19, 0x98, 0xd9, 0xbe, 0x51, 0x85, 0x7f, 0xbf, + 0x96, 0x9a, 0xeb, 0xbe, 0x37, 0xfe, 0x7f, 0x3f, 0x55, 0xb6, 0xa7, 0x3e, + 0xd5, 0xfb, 0x7f, 0x3f, 0x77, 0x75, 0xae, 0x3e, 0x15, 0xf6, 0x7f, 0xbf, + 0xfc, 0xe6, 0xda, 0xbe, 0x6e, 0x9f, 0x7f, 0xbf, 0x3e, 0xd3, 0xe6, 0xbe, + 0xcc, 0xfd, 0x7f, 0x3f, 0xba, 0x49, 0xb1, 0x3e, 0x89, 0xbd, 0x7f, 0x3f, + 0xbc, 0xb0, 0xac, 0x3e, 0000, 0000, 0000, 0000, 0xe5, 0x7c, 0000, 0xbf, + 0000, 0000, 0000, 0000, 0x77, 0x6d, 0xdb, 0xbe, 0000, 0000, 0000, 0000, + 0x0e, 0x0e, 0x82, 0x3e, 0000, 0000, 0000, 0000, 0xb6, 0xb8, 0xbc, 0x3e, + 0000, 0000, 0000, 0000, 0xc9, 0xe4, 0xe5, 0xbe, 0000, 0000, 0000, 0000, + 0x8d, 0x88, 0x02, 0xbf, 0000, 0000, 0000, 0000, 0x60, 0x6f, 0xce, 0x3e, + 0000, 0000, 0000, 0000, 0xb0, 0x72, 0xc6, 0x3e, 0000, 0000, 0000, 0000, + 0x4e, 0000, 0x01, 0xbf, 0000, 0000, 0000, 0000, 0x28, 0x37, 0xfa, 0xbe, + 0000, 0000, 0000, 0000, 0xa4, 0x06, 0xca, 0x3e, 0000, 0000, 0000, 0000, + 0x15, 0x83, 0xb8, 0x3e, 0000, 0000, 0000, 0000, 0x90, 0xaf, 0xff, 0xbe, + 0000, 0000, 0000, 0000, 0x07, 0x1c, 0xed, 0xbe, 0000, 0000, 0000, 0000, + 0x41, 0x02, 0xc1, 0x3e, 0000, 0000, 0000, 0000, 0x40, 0xdf, 0xd0, 0x3e, + 0000, 0000, 0000, 0000, 0xa1, 0x4f, 0xad, 0xbe, 0000, 0000, 0000, 0000, + 0xf5, 0xd8, 0x9c, 0xbe, 0000, 0000, 0000, 0000, 0x7d, 0xf9, 0xc5, 0x3e, + 0000, 0000, 0000, 0000, 0xd1, 0xc0, 0xce, 0x3e, 0000, 0000, 0000, 0000, + 0x8a, 0x8f, 0xb5, 0xbe, 0000, 0000, 0000, 0000, 0x2b, 0x6d, 0x94, 0xbe, + 0000, 0000, 0000, 0000, 0x4b, 0x33, 0xd2, 0x3e, 0000, 0000, 0000, 0000, + 0xd8, 0x7b, 0xce, 0x3e, 0000, 0000, 0000, 0000, 0xad, 0x91, 0x84, 0xbe, + 0000, 0000, 0000, 0000, 0x63, 0x51, 0x9c, 0xbe, 0000, 0000, 0000, 0000, + 0x96, 0x28, 0xca, 0x3e, 0000, 0000, 0000, 0000, 0x67, 0x7a, 0xe5, 0x3e, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x87, 0xf7, 0x26, 0xbf, + 0000, 0000, 0000, 0000, 0x09, 0xd7, 0xd1, 0xbe, 0xcb, 0xa5, 0x73, 0xbf, + 0x51, 0xd2, 0x6a, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0xbe, 0xa5, 0xe8, 0xbd, 0x7d, 0xe4, 0x42, 0x3c, 0xa6, 0x28, 0x91, 0xbe, + 0xcd, 0x64, 0xb0, 0xbe, 0x30, 0xb0, 0x7f, 0xbf, 0x02, 0x7a, 0xe8, 0xbe, + 0x43, 0x46, 0x7f, 0xbf, 0x9d, 0xf5, 0xdc, 0xbe, 0xfb, 0xc6, 0x7f, 0x3f, + 0xfd, 0x2d, 0xaa, 0x3e, 0x52, 0x74, 0x7f, 0x3f, 0x9e, 0x9a, 0xdf, 0x3e, + 0x53, 0xf3, 0x7f, 0xbf, 0xd3, 0x5c, 0xe1, 0xbe, 0x41, 0xde, 0x7e, 0xbf, + 0xb0, 0x06, 0xf6, 0xbe, 0x53, 0xc1, 0x7e, 0x3f, 0x77, 0xb8, 0xe0, 0x3e, + 0xe1, 0x66, 0x7e, 0x3f, 0x1d, 0xef, 0xe9, 0x3e, 0x81, 0xdd, 0x7e, 0xbf, + 0x79, 0x32, 0xeb, 0xbe, 0xdc, 0xe3, 0x7f, 0xbf, 0x6b, 0x0e, 0xe9, 0xbe, + 0xa7, 0x6a, 0x7e, 0x3f, 0x1d, 0x9a, 0xf2, 0x3e, 0xed, 0x2d, 0x7f, 0x3f, + 0xb2, 0x42, 0xea, 0x3e, 0xc7, 0xe4, 0x7e, 0xbf, 0x32, 0x31, 0xed, 0xbe, + 0x14, 0x81, 0x7f, 0xbf, 0xbb, 0x7c, 0xed, 0xbe, 0xbc, 0x23, 0x7f, 0x3f, + 0x4c, 0x1c, 0xeb, 0x3e, 0x9b, 0x58, 0x7f, 0x3f, 0xf6, 0x33, 0xdd, 0x3e, + 0x9d, 0xfe, 0x7f, 0xbf, 0xc7, 0xc1, 0xeb, 0xbe, 0x2e, 0xec, 0x7f, 0xbf, + 0x60, 0000, 0xfa, 0xbe, 0xd5, 0xd3, 0x7e, 0x3f, 0xba, 0xe8, 0xd4, 0x3e, + 0x83, 0xfa, 0x7e, 0x3f, 0xf2, 0xf4, 0xc6, 0x3e, 0x2f, 0xfe, 0x7f, 0xbf, + 0x02, 0x57, 0x02, 0xbf, 0x98, 0xfd, 0x7f, 0xbf, 0x9c, 0xc5, 0xfc, 0xbe, + 0x42, 0xb4, 0x7e, 0x3f, 0xd1, 0x7f, 0xc4, 0x3e, 0x51, 0xa3, 0x7e, 0x3f, + 0xa9, 0x66, 0xd6, 0x3e, 0x2e, 0xea, 0x7f, 0xbf, 0xf1, 0x18, 0xf6, 0xbe, + 0xf3, 0xe7, 0x7f, 0xbf, 0x7f, 0x17, 0xc7, 0xbe, 0x48, 0x93, 0x7d, 0x3f, + 0x5a, 0x27, 0xd9, 0x3e, 0x8a, 0x6c, 0x78, 0x3f, 0x91, 0x13, 0xdb, 0x3e, + 0x14, 0xb7, 0x7f, 0xbf, 0x39, 0xe8, 0x05, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x39, 0x62, 0x21, 0x3e, 0x62, 0x7f, 0x66, 0x3d, + 0x30, 0x45, 0x2b, 0xbe, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0xca, 0x21, 0x96, 0xbd, 0000, 0000, 0000, 0000, 0xe8, 0x31, 0xb1, 0xbd, + 0000, 0000, 0000, 0000, 0x4f, 0x5b, 0x70, 0x3d, 0000, 0000, 0000, 0000, + 0x5d, 0x0c, 0x94, 0x3d, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x51, 0x71, 0x44, 0xbe, 0xe2, 0x3b, 0x66, 0xbe, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xe6, 0x29, 0x1c, 0x3e, 0x8f, 0x8d, 0x27, 0x3e, + 0000, 0000, 0000, 0000, 0x6f, 0x0c, 0x1e, 0x3f, 0000, 0000, 0000, 0000, + 0x30, 0x14, 0x45, 0x3e, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x38, 0x09, 0x14, 0x3d, + 0000, 0000, 0000, 0000, 0x86, 0x99, 0x8f, 0x3e, 0xd5, 0x72, 0xb0, 0x3e, + 0x28, 0x5f, 0x6f, 0x3f, 0x89, 0x8f, 0x7f, 0x3f, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xc9, 0x2b, 0x67, 0xbe, + 0xcb, 0x4f, 0x20, 0xbe, 0x5f, 0xa9, 0x70, 0xbd, 0000, 0000, 0000, 0000, + 0x2b, 0xe7, 0x15, 0x3e, 0xfb, 0xee, 0xf9, 0x3d, 0xf5, 0xe7, 0x48, 0x3d, + 0x31, 0x63, 0xc0, 0x3d, 0xcf, 0x39, 0x12, 0x3f, 0x9d, 0x41, 0x85, 0x3e, + 0000, 0000, 0000, 0000, 0x2d, 0x55, 0x68, 0x3f, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x88, 0x63, 0xc6, 0x3b, 0000, 0000, 0000, 0000, 0x66, 0x94, 0xc7, 0x3b, + 0000, 0000, 0000, 0000, 0xed, 0xdc, 0xcc, 0x3b, 0000, 0000, 0000, 0000, + 0xca, 0x54, 0xcb, 0x3b, 0000, 0000, 0000, 0000, 0x8d, 0x08, 0xc8, 0x3b, + 0000, 0000, 0000, 0000, 0x48, 0xd9, 0xc6, 0x3b, 0000, 0000, 0000, 0000, + 0x3a, 0xa4, 0xcb, 0x3b, 0000, 0000, 0000, 0000, 0x17, 0x07, 0xcb, 0x3b, + 0000, 0000, 0000, 0000, 0x66, 0xc8, 0xc7, 0x3b, 0000, 0000, 0000, 0000, + 0xcd, 0x45, 0xc8, 0x3b, 0000, 0000, 0000, 0000, 0x7d, 0x6b, 0xca, 0x3b, + 0000, 0000, 0000, 0000, 0x10, 0xce, 0xca, 0x3b, 0000, 0000, 0000, 0000, + 0xc7, 0xc6, 0xc8, 0x3b, 0000, 0000, 0000, 0000, 0x76, 0x58, 0xc9, 0x3b, + 0000, 0000, 0000, 0000, 0x02, 0xed, 0xca, 0x3b, 0000, 0000, 0000, 0000, + 0x43, 0x70, 0xcc, 0x3b, 0000, 0000, 0000, 0000, 0x27, 0xbf, 0xc8, 0x3b, + 0000, 0000, 0000, 0000, 0x52, 0x6b, 0xc9, 0x3b, 0000, 0000, 0000, 0000, + 0x02, 0x61, 0xcd, 0x3b, 0000, 0000, 0000, 0000, 0x8e, 0xf1, 0xcc, 0x3b, + 0000, 0000, 0000, 0000, 0x78, 0x16, 0xc8, 0x3b, 0000, 0000, 0000, 0000, + 0x78, 0x7a, 0xc9, 0x3b, 0000, 0000, 0000, 0000, 0xbc, 0x8c, 0xcd, 0x3b, + 0000, 0000, 0000, 0000, 0xc8, 0xfc, 0xcd, 0x3b, 0000, 0000, 0000, 0000, + 0x2c, 0x7d, 0xc8, 0x3b, 0000, 0000, 0000, 0000, 0x42, 0x24, 0xc8, 0x3b, + 0000, 0000, 0000, 0000, 0xea, 0xa2, 0xc9, 0x3b, 0000, 0000, 0000, 0000, + 0x17, 0xf1, 0xc9, 0x3b, 0000, 0000, 0000, 0000, 0xb6, 0x27, 0xc8, 0x3b, + 0000, 0000, 0000, 0000, 0x54, 0x32, 0xc9, 0x3b, 0000, 0000, 0000, 0000, + 0xb1, 0x4e, 0xca, 0x3b, 0000, 0000, 0000, 0000, 0x03, 0x04, 0xcb, 0x3b, + 0x7f, 0xdc, 0x5d, 0xbf, 0xc3, 0xe1, 0xc7, 0x3b, 0x2d, 0x9e, 0xce, 0x3b, + 0xfd, 0xd3, 0xc8, 0x3b, 0x95, 0x2c, 0x7b, 0xbf, 0x98, 0x20, 0xd0, 0x3b, + 0x4f, 0x63, 0xd2, 0x3b, 0x74, 0x0d, 0xcd, 0x3b, 0x9e, 0x75, 0xcf, 0x3b, + 0xdd, 0xf9, 0xc7, 0x3b, 0x40, 0xa5, 0xce, 0x3b, 0000, 0xb9, 0xc8, 0x3b, + 0x2e, 0x66, 0xd1, 0x3b, 0x09, 0xf4, 0xcc, 0x3b, 0x75, 0x75, 0xd1, 0x3b, + 0x71, 0x5e, 0xcd, 0x3b, 0x78, 0x93, 0xce, 0x3b, 0x93, 0x52, 0xc8, 0x3b, + 0x3a, 0xce, 0xcf, 0x3b, 0x42, 0x59, 0xc8, 0x3b, 0x74, 0x91, 0xd0, 0x3b, + 0x2c, 0x15, 0xcd, 0x3b, 0x77, 0xa5, 0xd0, 0x3b, 0x09, 0xfc, 0xcc, 0x3b, + 0xa9, 0x8a, 0xd0, 0x3b, 0x44, 0x82, 0xc9, 0x3b, 0x76, 0x0b, 0xd0, 0x3b, + 0x0b, 0x07, 0xcc, 0x3b, 0xf9, 0xa2, 0xd0, 0x3b, 0xa9, 0xb0, 0xcd, 0x3b, + 0xe4, 0x58, 0xd2, 0x3b, 0x5b, 0x67, 0xca, 0x3b, 0x14, 0x70, 0xd1, 0x3b, + 0x43, 0x25, 0xcd, 0x3b, 0xf6, 0x08, 0xd3, 0x3b, 0x3f, 0x77, 0xca, 0x3b, + 0x06, 0x87, 0xd1, 0x3b, 0x18, 0x82, 0xc9, 0x3b, 0xd8, 0x81, 0xd3, 0x3b, + 0x75, 0x2c, 0xc9, 0x3b, 0x39, 0xc9, 0xd0, 0x3b, 0xbd, 0xb7, 0xca, 0x3b, + 0x42, 0x85, 0xd3, 0x3b, 0xd6, 0xae, 0xcb, 0x3b, 0x4a, 0xbb, 0xd3, 0x3b, + 0x6b, 0xd6, 0xc9, 0x3b, 0xf2, 0x46, 0xd7, 0x3b, 0xff, 0x7c, 0xcb, 0x3b, + 0x14, 0x42, 0xd2, 0x3b, 0xb7, 0x12, 0xc9, 0x3b, 0x71, 0xd0, 0xd0, 0x3b, + 0x17, 0x13, 0xca, 0x3b, 0x14, 0x28, 0xd6, 0x3b, 0x55, 0x7f, 0xcc, 0x3b, + 0x74, 0xfc, 0xd4, 0x3b, 0x74, 0x6c, 0xcd, 0x3b, 0x5d, 0x43, 0xd2, 0x3b, + 0xe4, 0xaf, 0xc6, 0x3b, 0x78, 0xd7, 0xd3, 0x3b, 0xbd, 0x4e, 0xc7, 0x3b, + 0x8f, 0xff, 0xd5, 0x3b, 0x3b, 0x09, 0xcd, 0x3b, 0xfa, 0x0a, 0xd7, 0x3b, + 0x95, 0xc6, 0xcb, 0x3b, 0000, 0000, 0000, 0000, 0xd9, 0xfe, 0xc8, 0x3b, + 0000, 0000, 0000, 0000, 0x58, 0x97, 0xca, 0x3b, 0000, 0000, 0000, 0000, + 0x4c, 0xf7, 0xca, 0x3b, 0000, 0000, 0000, 0000, 0x57, 0x8b, 0xc9, 0x3b, + 0000, 0000, 0000, 0000, 0x6e, 0xfc, 0xca, 0x3b, 0000, 0000, 0000, 0000, + 0x29, 0xf2, 0xc9, 0x3b, 0000, 0000, 0000, 0000, 0x5f, 0x6e, 0xc7, 0x3b, + 0000, 0000, 0000, 0000, 0xb5, 0x72, 0xc8, 0x3b, 0000, 0000, 0000, 0000, + 0x6e, 0x8f, 0xc8, 0x3b, 0000, 0000, 0000, 0000, 0x24, 0xb2, 0xca, 0x3b, + 0000, 0000, 0000, 0000, 0x45, 0xf6, 0xc6, 0x3b, 0000, 0000, 0000, 0000, + 0x4e, 0xaf, 0xc6, 0x3b, 0000, 0000, 0000, 0000, 0xb8, 0x06, 0xcb, 0x3b, + 0000, 0000, 0000, 0000, 0xe2, 0x2d, 0xca, 0x3b, 0000, 0000, 0000, 0000, + 0xed, 0x87, 0xc5, 0x3b, 0000, 0000, 0000, 0000, 0x80, 0x54, 0xc5, 0x3b, + 0000, 0000, 0000, 0000, 0x8c, 0xfa, 0xc6, 0x3b, 0000, 0000, 0000, 0000, + 0xe2, 0x4c, 0xc6, 0x3b, 0000, 0000, 0000, 0000, 0x77, 0x3b, 0xc5, 0x3b, + 0000, 0000, 0000, 0000, 0x3b, 0x3b, 0xc6, 0x3b, 0000, 0000, 0000, 0000, + 0xc0, 0x7b, 0xc6, 0x3b, 0000, 0000, 0000, 0000, 0xe1, 0xc7, 0xc5, 0x3b, + 0000, 0000, 0000, 0000, 0x47, 0x6e, 0xc6, 0x3b, 0000, 0000, 0000, 0000, + 0xbb, 0xe2, 0xc5, 0x3b, 0000, 0000, 0000, 0000, 0xa5, 0x57, 0xc4, 0x3b, + 0000, 0000, 0000, 0000, 0x29, 0x6b, 0xc5, 0x3b, 0000, 0000, 0000, 0000, + 0xee, 0x67, 0xc7, 0x3b, 0000, 0000, 0000, 0000, 0x7b, 0xf9, 0xc6, 0x3b, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xf0, 0x43, 0x42, 0x3f, 0000, 0000, 0000, 0000, + 0xe8, 0xb6, 0x10, 0x39, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0xe4, 0xda, 0x7d, 0xbf, 0x7c, 0xb8, 0x7e, 0xbf, 0x16, 0x29, 0x72, 0x3f, + 0x5b, 0x36, 0x70, 0x3f, 0x8b, 0x22, 0xd3, 0x3b, 0x4a, 0x5d, 0xc7, 0x3b, + 0xab, 0xb7, 0xd4, 0x3b, 0xf6, 0xa0, 0xc7, 0x3b, 0x5d, 0xd3, 0xd7, 0x3b, + 0xf3, 0x59, 0xcb, 0x3b, 0xdf, 0x49, 0xd2, 0x3b, 0xd8, 0xee, 0xcc, 0x3b, + 0x20, 0x2d, 0xd3, 0x3b, 0x27, 0xb2, 0xc6, 0x3b, 0x3e, 0x91, 0xcd, 0x3b, + 0x1c, 0xaa, 0xc6, 0x3b, 0xf2, 0x67, 0xd0, 0x3b, 0xe5, 0x7d, 0xcc, 0x3b, + 0xc9, 0x4d, 0xcf, 0x3b, 0x20, 0x94, 0xca, 0x3b, 0x93, 0xf5, 0xcb, 0x3b, + 0xbc, 0xc6, 0xc5, 0x3b, 0x73, 0x0d, 0xcd, 0x3b, 0x94, 0xe8, 0xc6, 0x3b, + 0xcd, 0xcc, 0xcf, 0x3b, 0x13, 0x91, 0xc9, 0x3b, 0xc5, 0x44, 0xd0, 0x3b, + 0xec, 0xe8, 0xcb, 0x3b, 0xc4, 0xdd, 0xcc, 0x3b, 0x17, 0x6f, 0xc5, 0x3b, + 0x53, 0x3e, 0xce, 0x3b, 0x87, 0x10, 0xc5, 0x3b, 0x39, 0x0e, 0xd1, 0x3b, + 0x83, 0x2a, 0xcc, 0x3b, 0xa1, 0xcf, 0xcf, 0x3b, 0x75, 0xc3, 0xcb, 0x3b, + 0x11, 0x8f, 0xd3, 0x3b, 0x9d, 0x90, 0xc5, 0x3b, 0x68, 0x7a, 0xd4, 0x3b, + 0x01, 0x87, 0xc5, 0x3b, 0x76, 0xc1, 0xd0, 0x3b, 0xd4, 0x67, 0xcc, 0x3b, + 0x58, 0x7c, 0xce, 0x3b, 0xad, 0x08, 0xcd, 0x3b, 0x20, 0x3b, 0xd5, 0x3b, + 0x66, 0x04, 0xc6, 0x3b, 0x43, 0xcc, 0xd5, 0x3b, 0x51, 0x51, 0xc7, 0x3b, + 0x46, 0xdd, 0xd0, 0x3b, 0xf1, 0x96, 0xcd, 0x3b, 0x89, 0xa2, 0xcf, 0x3b, + 0x72, 0xac, 0xcd, 0x3b, 0x2f, 0xf8, 0xd8, 0x3b, 0x07, 0xd2, 0xc8, 0x3b, + 0x81, 0x93, 0xb1, 0xbc, 0x50, 0x63, 0xc6, 0x3b, 0x14, 0x3c, 0xd1, 0x3b, + 0x40, 0xc2, 0xcd, 0x3b, 0xde, 0x15, 0x60, 0xbe, 0x89, 0x49, 0xcd, 0x3b, + 0x96, 0xef, 0x03, 0x3a, 0xe8, 0x51, 0xea, 0x39, 0000, 0000, 0x80, 0x3f, + 0000, 0000, 0000, 0000, 0xc5, 0xd0, 0x78, 0xbf, 0xe6, 0x89, 0x9e, 0x39, + 0x8a, 0x89, 0x7a, 0x3f, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x1c, 0x7c, 0x73, 0xbf, 0000, 0000, 0000, 0000, 0x7b, 0x75, 0x73, 0x3f, + 0000, 0000, 0000, 0000, 0xbf, 0x29, 0x77, 0xbf, 0000, 0000, 0000, 0000, + 0xd4, 0x0b, 0x73, 0x3f, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0x80, 0xbf, + 0x3d, 0xf3, 0x6f, 0x3f, 0xe0, 0x1d, 0x79, 0x3f, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0x80, 0xbf, 0x2b, 0x07, 0x6f, 0x3f, 0xfb, 0x33, 0x7c, 0x3f, + 0000, 0000, 0000, 0000, 0x25, 0xdb, 0xc4, 0xbd, 0000, 0000, 0000, 0000, + 0xb6, 0x24, 0x71, 0x3f, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xef, 0xc0, 0x7f, 0xbf, + 0000, 0000, 0x80, 0xbf, 0x9b, 0xb2, 0x71, 0x3f, 0xe9, 0x2b, 0x70, 0x3f, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, 0x66, 0x01, 0xe9, 0xbe, + 0x65, 0xf8, 0x6f, 0x3f, 0xf3, 0x97, 0x72, 0x3f, 0000, 0000, 0x80, 0xbf, + 0x2c, 0x82, 0xfe, 0xbe, 0x5a, 0xff, 0x6f, 0x3f, 0xb1, 0xec, 0x6f, 0x3f, + 0x81, 0x1f, 0x7e, 0xbf, 0000, 0000, 0000, 0000, 0x69, 0xb1, 0x71, 0x3f, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x52, 0xfa, 0x5a, 0xbf, 0000, 0000, 0000, 0000, 0xd0, 0xf8, 0x5c, 0xbf, + 0000, 0000, 0000, 0000, 0x06, 0x7f, 0x69, 0xbf, 0000, 0000, 0000, 0000, + 0xc9, 0x8f, 0x6d, 0xbf, 0000, 0000, 0000, 0000, 0x6c, 0x2c, 0x61, 0xbf, + 0000, 0000, 0000, 0000, 0x2c, 0x1c, 0x5e, 0xbf, 0000, 0000, 0000, 0000, + 0x5a, 0x40, 0x6c, 0xbf, 0000, 0000, 0000, 0000, 0xfa, 0xcc, 0x6e, 0xbf, + 0000, 0000, 0000, 0000, 0x9f, 0xfc, 0x5c, 0xbf, 0000, 0000, 0000, 0000, + 0x13, 0x27, 0x5d, 0xbf, 0000, 0000, 0000, 0000, 0x6e, 0x14, 0x6c, 0xbf, + 0000, 0000, 0000, 0000, 0x84, 0x37, 0x6b, 0xbf, 0000, 0000, 0000, 0000, + 0x73, 0x4b, 0x5a, 0xbf, 0000, 0000, 0000, 0000, 0x65, 0x04, 0x5b, 0xbf, + 0000, 0000, 0000, 0000, 0xe1, 0x36, 0x6c, 0xbf, 0000, 0000, 0000, 0000, + 0x94, 0xbf, 0x6b, 0xbf, 0000, 0000, 0000, 0000, 0x19, 0xc2, 0x57, 0xbf, + 0000, 0000, 0000, 0000, 0x2f, 0x85, 0x5a, 0xbf, 0000, 0000, 0000, 0000, + 0xe6, 0x9a, 0x6a, 0xbf, 0000, 0000, 0000, 0000, 0x78, 0x62, 0x6a, 0xbf, + 0000, 0000, 0000, 0000, 0x4f, 0xe7, 0x59, 0xbf, 0000, 0000, 0000, 0000, + 0xe1, 0x83, 0x5d, 0xbf, 0000, 0000, 0000, 0000, 0x9d, 0x47, 0x6b, 0xbf, + 0000, 0000, 0000, 0000, 0x4a, 0xb8, 0x70, 0xbf, 0000, 0000, 0000, 0000, + 0x63, 0x20, 0x59, 0xbf, 0000, 0000, 0000, 0000, 0x3f, 0x89, 0x59, 0xbf, + 0000, 0000, 0000, 0000, 0xff, 0x41, 0x77, 0xbf, 0000, 0000, 0000, 0000, + 0x7c, 0x2a, 0x75, 0xbf, 0000, 0000, 0000, 0000, 0x99, 0x26, 0x59, 0xbf, + 0000, 0000, 0000, 0000, 0xe0, 0x9a, 0x5d, 0xbf, 0000, 0000, 0000, 0000, + 0xa7, 0x54, 0x76, 0xbf, 0000, 0000, 0000, 0000, 0x0a, 0x65, 0x76, 0xbf, + 0xc5, 0x8c, 0x90, 0x3c, 0xb9, 0x87, 0x5e, 0x3f, 0x8c, 0xac, 0xc0, 0xbc, + 0x1e, 0xaa, 0x5c, 0x3f, 0x82, 0xae, 0x53, 0xbc, 0xb2, 0x78, 0x65, 0x3f, + 0x59, 0xfe, 0x96, 0xbb, 0x5b, 0x73, 0x6d, 0x3f, 0x3d, 0xf8, 0x9b, 0xbb, + 0x68, 0xaf, 0x5d, 0x3f, 0x63, 0xd0, 0x2e, 0xbb, 0x92, 0xa9, 0x5b, 0x3f, + 0x57, 0x20, 0x14, 0x3c, 0x68, 0x87, 0x6d, 0x3f, 0xfa, 0x4b, 0x52, 0xbc, + 0x4a, 0x80, 0x6b, 0x3f, 0x86, 0xbe, 0x27, 0x3c, 0x09, 0x30, 0x5b, 0x3f, + 0x90, 0x58, 0xf1, 0x3c, 0x06, 0x43, 0x59, 0x3f, 0xcb, 0xc4, 0x3a, 0xbc, + 0xf7, 0x2f, 0x6c, 0x3f, 0x65, 0xaa, 0xc7, 0xbc, 0x77, 0x4a, 0x6d, 0x3f, + 0xaf, 0x76, 0x6f, 0x3b, 0x3a, 0x9b, 0x59, 0x3f, 0x21, 0x7d, 0xf7, 0x3c, + 0x0e, 0x17, 0x61, 0x3f, 0x03, 0x3d, 0x5f, 0xbc, 0xee, 0xb5, 0x6e, 0x3f, + 0x3b, 0x40, 0x76, 0x3c, 0x0f, 0xc2, 0x6a, 0x3f, 0x91, 0x1c, 0x51, 0x3d, + 0x23, 0x44, 0x61, 0x3f, 0x45, 0xe5, 0xeb, 0x3c, 0x5c, 0xff, 0x6d, 0x3f, + 0x7c, 0x9c, 0x35, 0x3a, 0x36, 0xb5, 0x67, 0x3f, 0x13, 0x0f, 0x59, 0xbc, + 0xbc, 0x44, 0x69, 0x3f, 0x3e, 0xe3, 0xbb, 0xbc, 0xa2, 0x15, 0x6b, 0x3f, + 0xd4, 0xa4, 0x15, 0xbd, 0x5b, 0x17, 0x67, 0x3f, 0x8f, 0x4a, 0x3c, 0xbd, + 0x07, 0x76, 0x67, 0x3f, 0xbf, 0xd1, 0x93, 0xbd, 0xff, 0x32, 0x6d, 0x3f, + 0xb7, 0xd6, 0x80, 0xbd, 0xbe, 0xb9, 0x67, 0x3f, 0x7a, 0x24, 0x79, 0xbd, + 0xb0, 0x47, 0x63, 0x3f, 0xbf, 0x0a, 0x60, 0xbb, 0xfc, 0xde, 0x71, 0x3f, + 0xca, 0x0c, 0x17, 0xbc, 0xcc, 0xac, 0x70, 0x3f, 0x81, 0x77, 0x84, 0xbc, + 0xdf, 0x6a, 0x67, 0x3f, 0x4e, 0xb2, 0x5c, 0xbd, 0xb5, 0x80, 0x64, 0x3f, + 0x04, 0x6f, 0xa2, 0x3b, 0x58, 0x28, 0x70, 0x3f, 0x58, 0x74, 0x36, 0x3d, + 0x65, 0xfe, 0x70, 0x3f, 0000, 0000, 0000, 0000, 0x20, 0x6a, 0x5d, 0xbf, + 0000, 0000, 0000, 0000, 0xf8, 0x4a, 0x67, 0xbf, 0000, 0000, 0000, 0000, + 0x2b, 0x99, 0x77, 0xbf, 0000, 0000, 0000, 0000, 0x3d, 0xf8, 0x6d, 0xbf, + 0000, 0000, 0000, 0000, 0xb9, 0xbc, 0x64, 0xbf, 0000, 0000, 0000, 0000, + 0xfb, 0x36, 0x5c, 0xbf, 0000, 0000, 0000, 0000, 0xa2, 0x43, 0x6a, 0xbf, + 0000, 0000, 0000, 0000, 0xbe, 0xfb, 0x6b, 0xbf, 0000, 0000, 0000, 0000, + 0xab, 0x1d, 0x5d, 0xbf, 0000, 0000, 0000, 0000, 0x7e, 0x57, 0x5f, 0xbf, + 0000, 0000, 0000, 0000, 0x25, 0x39, 0x6b, 0xbf, 0000, 0000, 0000, 0000, + 0x2d, 0xcb, 0x6e, 0xbf, 0000, 0000, 0000, 0000, 0x96, 0xc9, 0x5d, 0xbf, + 0000, 0000, 0000, 0000, 0x65, 0xe3, 0x62, 0xbf, 0000, 0000, 0000, 0000, + 0xd1, 0x1b, 0x6d, 0xbf, 0000, 0000, 0000, 0000, 0x40, 0xb9, 0x69, 0xbf, + 0000, 0000, 0000, 0000, 0xef, 0xe1, 0x70, 0xbf, 0000, 0000, 0000, 0000, + 0xda, 0xaf, 0x73, 0xbf, 0000, 0000, 0000, 0000, 0x3a, 0x15, 0x6c, 0xbf, + 0000, 0000, 0000, 0000, 0xb2, 0x31, 0x6a, 0xbf, 0000, 0000, 0000, 0000, + 0xcc, 0x5b, 0x6f, 0xbf, 0000, 0000, 0000, 0000, 0x87, 0000, 0x75, 0xbf, + 0000, 0000, 0000, 0000, 0xfa, 0x6c, 0x69, 0xbf, 0000, 0000, 0000, 0000, + 0xe9, 0x40, 0x6a, 0xbf, 0000, 0000, 0000, 0000, 0xda, 0x43, 0x77, 0xbf, + 0000, 0000, 0000, 0000, 0xa4, 0xc5, 0x73, 0xbf, 0000, 0000, 0000, 0000, + 0xd8, 0x31, 0x6b, 0xbf, 0000, 0000, 0000, 0000, 0x7a, 0xd7, 0x64, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x8c, 0x0e, 0x42, 0xbf, + 0000, 0000, 0000, 0000, 0x4b, 0x92, 0x01, 0xbf, 0x41, 0x1f, 0x9d, 0xbe, + 0000, 0xe9, 0xcb, 0xbe, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0xad, 0xec, 0x7b, 0xbd, 0x6b, 0x09, 0xcb, 0xbd, 0x30, 0x52, 0x21, 0xbe, + 0x78, 0x13, 0xee, 0x3c, 0x5b, 0x5a, 0x48, 0xbd, 0x76, 0x15, 0x64, 0x3f, + 0x87, 0x81, 0x99, 0xbd, 0x9b, 0xed, 0x66, 0x3f, 0xac, 0xaf, 0x28, 0x3d, + 0x99, 0x70, 0x71, 0x3f, 0xb3, 0xfe, 0x84, 0xbd, 0x64, 0x4a, 0x66, 0x3f, + 0x76, 0x31, 0x98, 0xbc, 0xa9, 0xdc, 0x65, 0x3f, 0x1e, 0xf0, 0xbf, 0x3d, + 0xa4, 0x80, 0x60, 0x3f, 0xa7, 0x4c, 0xc9, 0xbd, 0xcc, 0x04, 0x66, 0x3f, + 0xa3, 0x1d, 0xe4, 0xbd, 0xf7, 0xb5, 0x63, 0x3f, 0xdd, 0x31, 0xc0, 0x3d, + 0xb4, 0x62, 0x63, 0x3f, 0x6c, 0x80, 0xea, 0x3c, 0x96, 0xef, 0x63, 0x3f, + 0xe7, 0x0e, 0xe3, 0xbd, 0x97, 0x6e, 0x61, 0x3f, 0xe1, 0x54, 0xa3, 0xbd, + 0x77, 0xa0, 0x63, 0x3f, 0xec, 0xc3, 0xbd, 0x3d, 0xec, 0xdd, 0x62, 0x3f, + 0x47, 0x7d, 0x7d, 0x3d, 0x2c, 0xca, 0x62, 0x3f, 0xd3, 0x42, 0xa7, 0xbd, + 0x58, 0x68, 0x63, 0x3f, 0x47, 0xb5, 0x91, 0xbd, 0x9f, 0xde, 0x66, 0x3f, + 0xa8, 0xe7, 0x46, 0x3a, 0x97, 0x3d, 0x63, 0x3f, 0x2c, 0x55, 0xc2, 0xbc, + 0xe7, 0x66, 0x5f, 0x3f, 0x16, 0x5b, 0xc3, 0xbd, 0x92, 0xcf, 0x68, 0x3f, + 0xba, 0x4f, 0xb6, 0xbd, 0x40, 0xe0, 0x6b, 0x3f, 0x42, 0x4c, 0x6c, 0xbb, + 0x5f, 0x54, 0x5c, 0x3f, 0xcb, 0x14, 0xb6, 0x3b, 0x02, 0x9f, 0x5e, 0x3f, + 0xd5, 0x63, 0xcd, 0xbd, 0xf2, 0x63, 0x6c, 0x3f, 0x0f, 0x95, 0xd2, 0xbd, + 0xdd, 0x77, 0x68, 0x3f, 0x22, 0x52, 0xcc, 0x3c, 0x9c, 0x7b, 0x60, 0x3f, + 0x51, 0x16, 0x85, 0x3c, 0x0d, 0xd9, 0x6b, 0x3f, 0x3e, 0x73, 0x0c, 0xbe, + 0x1d, 0xd4, 0x67, 0x3f, 0x92, 0x18, 0xd1, 0xbd, 0x3b, 0x60, 0x67, 0x3f, + 0x96, 0x29, 0x41, 0x3d, 0x77, 0x2f, 0x5a, 0x3f, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xdc, 0xd7, 0x32, 0x3e, 0x25, 0x98, 0x7f, 0x3f, + 0xdd, 0x74, 0xf4, 0x3d, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x13, 0x9c, 0x99, 0xbe, 0000, 0000, 0000, 0000, 0x71, 0xf6, 0x97, 0xbe, + 0000, 0000, 0000, 0000, 0x3d, 0xef, 0x81, 0xbe, 0000, 0000, 0000, 0000, + 0xc1, 0x7c, 0x9c, 0xbe, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x78, 0xf8, 0x94, 0xbe, 0x97, 0xd6, 0x4c, 0x3d, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x48, 0xdc, 0xa5, 0xbe, 0x0d, 0xa9, 0x53, 0x3d, + 0000, 0000, 0000, 0000, 0xb9, 0xe0, 0x47, 0xbf, 0000, 0000, 0000, 0000, + 0xca, 0xd4, 0x8c, 0xbe, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x2c, 0x95, 0xcb, 0xbc, + 0000, 0000, 0000, 0000, 0x50, 0x39, 0x31, 0xbe, 0x22, 0xf9, 0x05, 0x3d, + 0x92, 0x84, 0xb5, 0xbe, 0x2b, 0xdc, 0x6f, 0x3d, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xa9, 0x81, 0x5c, 0x3f, + 0x7b, 0x4c, 0x9f, 0x3e, 0x95, 0xb9, 0xa0, 0x3e, 0000, 0000, 0000, 0000, + 0x84, 0xf2, 0x5a, 0x3f, 0x05, 0xde, 0xa6, 0x3e, 0xb7, 0xcc, 0xb0, 0x3e, + 0xae, 0xc5, 0x9b, 0x3d, 0xe9, 0x20, 0x52, 0x3f, 0xda, 0x1e, 0x4f, 0x3e, + 0000, 0000, 0000, 0000, 0xfb, 0x02, 0xd7, 0x3e, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x00 +}; + +// this is an adaptive octree +static const unsigned char octree_3[] = { + 0x5f, 0x4f, 0x43, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x31, 0x2e, 0x30, 0x5f, + 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, + 0x02, 0000, 0000, 0000, 0x04, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0x01, 0000, 0000, + 0x01, 0000, 0000, 0000, 0x08, 0000, 0000, 0000, 0x40, 0000, 0000, 0000, + 0x08, 0000, 0000, 0000, 0x18, 0000, 0000, 0000, 0x38, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x09, 0000, 0000, 0000, 0x49, 0000, 0000, 0000, 0x51, 0000, 0000, 0000, + 0x69, 0000, 0000, 0000, 0xa1, 0000, 0000, 0000, 0xa1, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x08, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x03, 0000, 0000, 0000, + 0x07, 0000, 0000, 0000, 0x0d, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x2b, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x04, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x01, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0x42, 0000, 0000, 0000, 0x42, 0000, 0000, 0000, 0x42, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xcc, 0x02, 0000, 0000, 0x50, 0x05, 0000, 0000, + 0xd4, 0x07, 0000, 0000, 0xd4, 0x07, 0000, 0000, 0xe4, 0x11, 0000, 0000, + 0xe4, 0x11, 0000, 0000, 0x68, 0x14, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0x01, 0000, 0000, 0x01, 0x01, 0000, 0x01, 0000, 0x01, + 0000, 0x01, 0x01, 0x01, 0x01, 0000, 0000, 0x01, 0x01, 0000, 0x01, 0x01, + 0x01, 0x01, 0000, 0x01, 0x01, 0x01, 0x01, 0x01, 0000, 0000, 0000, 0x02, + 0000, 0000, 0x01, 0x02, 0000, 0x01, 0000, 0x02, 0000, 0x01, 0x01, 0x02, + 0x01, 0000, 0000, 0x02, 0x01, 0000, 0x01, 0x02, 0x01, 0x01, 0000, 0x02, + 0x01, 0x01, 0x01, 0x02, 0000, 0000, 0x02, 0x02, 0000, 0000, 0x03, 0x02, + 0000, 0x01, 0x02, 0x02, 0000, 0x01, 0x03, 0x02, 0x01, 0000, 0x02, 0x02, + 0x01, 0000, 0x03, 0x02, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x03, 0x02, + 0000, 0x02, 0000, 0x02, 0000, 0x02, 0x01, 0x02, 0000, 0x03, 0000, 0x02, + 0000, 0x03, 0x01, 0x02, 0x01, 0x02, 0000, 0x02, 0x01, 0x02, 0x01, 0x02, + 0x01, 0x03, 0000, 0x02, 0x01, 0x03, 0x01, 0x02, 0000, 0x02, 0x02, 0x02, + 0000, 0x02, 0x03, 0x02, 0000, 0x03, 0x02, 0x02, 0000, 0x03, 0x03, 0x02, + 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x03, 0x02, 0x01, 0x03, 0x02, 0x02, + 0x01, 0x03, 0x03, 0x02, 0x02, 0000, 0000, 0x02, 0x02, 0000, 0x01, 0x02, + 0x02, 0x01, 0000, 0x02, 0x02, 0x01, 0x01, 0x02, 0x03, 0000, 0000, 0x02, + 0x03, 0000, 0x01, 0x02, 0x03, 0x01, 0000, 0x02, 0x03, 0x01, 0x01, 0x02, + 0x02, 0000, 0x02, 0x02, 0x02, 0000, 0x03, 0x02, 0x02, 0x01, 0x02, 0x02, + 0x02, 0x01, 0x03, 0x02, 0x03, 0000, 0x02, 0x02, 0x03, 0000, 0x03, 0x02, + 0x03, 0x01, 0x02, 0x02, 0x03, 0x01, 0x03, 0x02, 0x02, 0x02, 0000, 0x02, + 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0000, 0x02, 0x02, 0x03, 0x01, 0x02, + 0x03, 0x02, 0000, 0x02, 0x03, 0x02, 0x01, 0x02, 0x03, 0x03, 0000, 0x02, + 0x03, 0x03, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, + 0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03, 0x02, 0x03, 0x02, 0x02, 0x02, + 0x03, 0x02, 0x03, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, 0x03, 0x02, + 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x05, 0x04, 0x03, + 0x04, 0x05, 0x05, 0x03, 0x05, 0x04, 0x04, 0x03, 0x05, 0x04, 0x05, 0x03, + 0x05, 0x05, 0x04, 0x03, 0x05, 0x05, 0x05, 0x03, 0x08, 0x08, 0x08, 0x04, + 0x08, 0x08, 0x09, 0x04, 0x08, 0x09, 0x08, 0x04, 0x08, 0x09, 0x09, 0x04, + 0x09, 0x08, 0x08, 0x04, 0x09, 0x08, 0x09, 0x04, 0x09, 0x09, 0x08, 0x04, + 0x09, 0x09, 0x09, 0x04, 0x08, 0x08, 0x0a, 0x04, 0x08, 0x08, 0x0b, 0x04, + 0x08, 0x09, 0x0a, 0x04, 0x08, 0x09, 0x0b, 0x04, 0x09, 0x08, 0x0a, 0x04, + 0x09, 0x08, 0x0b, 0x04, 0x09, 0x09, 0x0a, 0x04, 0x09, 0x09, 0x0b, 0x04, + 0x08, 0x0a, 0x08, 0x04, 0x08, 0x0a, 0x09, 0x04, 0x08, 0x0b, 0x08, 0x04, + 0x08, 0x0b, 0x09, 0x04, 0x09, 0x0a, 0x08, 0x04, 0x09, 0x0a, 0x09, 0x04, + 0x09, 0x0b, 0x08, 0x04, 0x09, 0x0b, 0x09, 0x04, 0x12, 0x10, 0x10, 0x05, + 0x12, 0x10, 0x11, 0x05, 0x12, 0x11, 0x10, 0x05, 0x12, 0x11, 0x11, 0x05, + 0x13, 0x10, 0x10, 0x05, 0x13, 0x10, 0x11, 0x05, 0x13, 0x11, 0x10, 0x05, + 0x13, 0x11, 0x11, 0x05, 0x12, 0x10, 0x12, 0x05, 0x12, 0x10, 0x13, 0x05, + 0x12, 0x11, 0x12, 0x05, 0x12, 0x11, 0x13, 0x05, 0x13, 0x10, 0x12, 0x05, + 0x13, 0x10, 0x13, 0x05, 0x13, 0x11, 0x12, 0x05, 0x13, 0x11, 0x13, 0x05, + 0x12, 0x12, 0x10, 0x05, 0x12, 0x12, 0x11, 0x05, 0x12, 0x13, 0x10, 0x05, + 0x12, 0x13, 0x11, 0x05, 0x13, 0x12, 0x10, 0x05, 0x13, 0x12, 0x11, 0x05, + 0x13, 0x13, 0x10, 0x05, 0x13, 0x13, 0x11, 0x05, 0x10, 0x12, 0x14, 0x05, + 0x10, 0x12, 0x15, 0x05, 0x10, 0x13, 0x14, 0x05, 0x10, 0x13, 0x15, 0x05, + 0x11, 0x12, 0x14, 0x05, 0x11, 0x12, 0x15, 0x05, 0x11, 0x13, 0x14, 0x05, + 0x11, 0x13, 0x15, 0x05, 0x12, 0x10, 0x14, 0x05, 0x12, 0x10, 0x15, 0x05, + 0x12, 0x11, 0x14, 0x05, 0x12, 0x11, 0x15, 0x05, 0x13, 0x10, 0x14, 0x05, + 0x13, 0x10, 0x15, 0x05, 0x13, 0x11, 0x14, 0x05, 0x13, 0x11, 0x15, 0x05, + 0x10, 0x14, 0x12, 0x05, 0x10, 0x14, 0x13, 0x05, 0x10, 0x15, 0x12, 0x05, + 0x10, 0x15, 0x13, 0x05, 0x11, 0x14, 0x12, 0x05, 0x11, 0x14, 0x13, 0x05, + 0x11, 0x15, 0x12, 0x05, 0x11, 0x15, 0x13, 0x05, 0x12, 0x14, 0x10, 0x05, + 0x12, 0x14, 0x11, 0x05, 0x12, 0x15, 0x10, 0x05, 0x12, 0x15, 0x11, 0x05, + 0x13, 0x14, 0x10, 0x05, 0x13, 0x14, 0x11, 0x05, 0x13, 0x15, 0x10, 0x05, + 0x13, 0x15, 0x11, 0x05, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x01, 0000, 0000, 0000, 0x02, 0000, 0000, 0000, 0x03, 0000, 0000, 0000, + 0x04, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, 0x06, 0000, 0000, 0000, + 0x07, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, + 0x01, 0000, 0000, 0000, 0x02, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, + 0x01, 0000, 0000, 0000, 0x02, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x04, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x05, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x06, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x02, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x03, 0000, 0000, 0000, 0x04, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x05, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x06, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x07, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x08, 0000, 0000, 0000, 0x09, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0a, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x0b, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x0c, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x58, 0xe4, 0x31, 0xbf, 0x15, 0xb6, 0x1d, 0xbf, + 0x15, 0xb6, 0x1d, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x7f, 0x2c, 0x02, 0xbf, 0xad, 0xa6, 0x49, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x7f, 0x2c, 0x02, 0xbf, + 0000, 0000, 0000, 0000, 0xad, 0xa6, 0x49, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xf9, 0xe6, 0x74, 0x3e, 0xe7, 0xca, 0xc3, 0x3e, + 0xe7, 0xca, 0xc3, 0x3e, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x3a, 0xcd, 0x13, 0xbf, 0xf3, 0x04, 0x35, 0xbf, 0xf3, 0x04, 0x35, 0xbf, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x77, 0xa1, 0x2f, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x77, 0xa1, 0x2f, 0xbf, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x3a, 0xcd, 0x13, 0xbf, 0xf3, 0x04, 0x35, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xf3, 0x04, 0x35, 0xbf, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x8c, 0x40, 0x3a, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x3a, 0xcd, 0x13, 0xbf, 0000, 0000, 0000, 0000, 0xf3, 0x04, 0x35, 0xbf, + 0000, 0000, 0000, 0000, 0xf3, 0x04, 0x35, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x8b, 0x40, 0x3a, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0xff, 0x23, 0xbf, 0x3c, 0x3a, 0x51, 0xe7, 0x3d, 0x3a, 0x51, 0xe7, 0x3d, + 0x76, 0xb6, 0x99, 0x3e, 0x3a, 0x51, 0xe7, 0x3d, 0x76, 0xb6, 0x99, 0x3e, + 0x76, 0xb6, 0x99, 0x3e, 0000, 0000, 0000, 0000, 0x34, 0x3f, 0xea, 0x3d, + 0000, 0000, 0000, 0000, 0xc0, 0xb6, 0x99, 0x3e, 0000, 0000, 0000, 0000, + 0x76, 0xb6, 0x99, 0x3e, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x33, 0x3f, 0xea, 0x3d, 0xc0, 0xb6, 0x99, 0x3e, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x76, 0xb6, 0x99, 0x3e, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xf3, 0x04, 0x35, 0xbf, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xf3, 0x04, 0x35, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x85, 0x94, 0x2f, 0xbe, 0x80, 0x27, 0xbd, 0x3c, 0x80, 0x27, 0xbd, 0x3c, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0x27, 0xbd, 0x3c, + 0x80, 0x27, 0xbd, 0x3c, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x80, 0x27, 0xbd, 0x3c, 0000, 0000, 0000, 0000, + 0x80, 0x27, 0xbd, 0x3c, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0xbd, 0x30, 0xbd, 0x3c, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0x27, 0xbd, 0x3c, + 0x80, 0x27, 0xbd, 0x3c, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xbd, 0x30, 0xbd, 0x3c, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x80, 0x27, 0xbd, 0x3c, 0000, 0000, 0000, 0000, 0x80, 0x27, 0xbd, 0x3c, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x00 +}; + +// this is an adaptive octree +static const unsigned char octree_4[] = { + 0x5f, 0x4f, 0x43, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x31, 0x2e, 0x30, 0x5f, + 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, + 0x02, 0000, 0000, 0000, 0x04, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0x01, 0000, 0000, + 0x01, 0000, 0000, 0000, 0x08, 0000, 0000, 0000, 0x40, 0000, 0000, 0000, + 0x08, 0000, 0000, 0000, 0x20, 0000, 0000, 0000, 0x40, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x09, 0000, 0000, 0000, 0x49, 0000, 0000, 0000, 0x51, 0000, 0000, 0000, + 0x71, 0000, 0000, 0000, 0xb1, 0000, 0000, 0000, 0xb1, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x08, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x04, 0000, 0000, 0000, + 0x08, 0000, 0000, 0000, 0x11, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x2b, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x04, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x01, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0x42, 0000, 0000, 0000, 0x42, 0000, 0000, 0000, 0x42, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xcc, 0x02, 0000, 0000, 0x90, 0x05, 0000, 0000, + 0x54, 0x08, 0000, 0000, 0x54, 0x08, 0000, 0000, 0x64, 0x13, 0000, 0000, + 0x64, 0x13, 0000, 0000, 0x28, 0x16, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0x01, 0000, 0000, 0x01, 0x01, 0000, 0x01, 0000, 0x01, + 0000, 0x01, 0x01, 0x01, 0x01, 0000, 0000, 0x01, 0x01, 0000, 0x01, 0x01, + 0x01, 0x01, 0000, 0x01, 0x01, 0x01, 0x01, 0x01, 0000, 0000, 0000, 0x02, + 0000, 0000, 0x01, 0x02, 0000, 0x01, 0000, 0x02, 0000, 0x01, 0x01, 0x02, + 0x01, 0000, 0000, 0x02, 0x01, 0000, 0x01, 0x02, 0x01, 0x01, 0000, 0x02, + 0x01, 0x01, 0x01, 0x02, 0000, 0000, 0x02, 0x02, 0000, 0000, 0x03, 0x02, + 0000, 0x01, 0x02, 0x02, 0000, 0x01, 0x03, 0x02, 0x01, 0000, 0x02, 0x02, + 0x01, 0000, 0x03, 0x02, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x03, 0x02, + 0000, 0x02, 0000, 0x02, 0000, 0x02, 0x01, 0x02, 0000, 0x03, 0000, 0x02, + 0000, 0x03, 0x01, 0x02, 0x01, 0x02, 0000, 0x02, 0x01, 0x02, 0x01, 0x02, + 0x01, 0x03, 0000, 0x02, 0x01, 0x03, 0x01, 0x02, 0000, 0x02, 0x02, 0x02, + 0000, 0x02, 0x03, 0x02, 0000, 0x03, 0x02, 0x02, 0000, 0x03, 0x03, 0x02, + 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x03, 0x02, 0x01, 0x03, 0x02, 0x02, + 0x01, 0x03, 0x03, 0x02, 0x02, 0000, 0000, 0x02, 0x02, 0000, 0x01, 0x02, + 0x02, 0x01, 0000, 0x02, 0x02, 0x01, 0x01, 0x02, 0x03, 0000, 0000, 0x02, + 0x03, 0000, 0x01, 0x02, 0x03, 0x01, 0000, 0x02, 0x03, 0x01, 0x01, 0x02, + 0x02, 0000, 0x02, 0x02, 0x02, 0000, 0x03, 0x02, 0x02, 0x01, 0x02, 0x02, + 0x02, 0x01, 0x03, 0x02, 0x03, 0000, 0x02, 0x02, 0x03, 0000, 0x03, 0x02, + 0x03, 0x01, 0x02, 0x02, 0x03, 0x01, 0x03, 0x02, 0x02, 0x02, 0000, 0x02, + 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0000, 0x02, 0x02, 0x03, 0x01, 0x02, + 0x03, 0x02, 0000, 0x02, 0x03, 0x02, 0x01, 0x02, 0x03, 0x03, 0000, 0x02, + 0x03, 0x03, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, + 0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03, 0x02, 0x03, 0x02, 0x02, 0x02, + 0x03, 0x02, 0x03, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, 0x03, 0x02, + 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x05, 0x04, 0x03, + 0x04, 0x05, 0x05, 0x03, 0x05, 0x04, 0x04, 0x03, 0x05, 0x04, 0x05, 0x03, + 0x05, 0x05, 0x04, 0x03, 0x05, 0x05, 0x05, 0x03, 0x08, 0x08, 0x08, 0x04, + 0x08, 0x08, 0x09, 0x04, 0x08, 0x09, 0x08, 0x04, 0x08, 0x09, 0x09, 0x04, + 0x09, 0x08, 0x08, 0x04, 0x09, 0x08, 0x09, 0x04, 0x09, 0x09, 0x08, 0x04, + 0x09, 0x09, 0x09, 0x04, 0x08, 0x0a, 0x08, 0x04, 0x08, 0x0a, 0x09, 0x04, + 0x08, 0x0b, 0x08, 0x04, 0x08, 0x0b, 0x09, 0x04, 0x09, 0x0a, 0x08, 0x04, + 0x09, 0x0a, 0x09, 0x04, 0x09, 0x0b, 0x08, 0x04, 0x09, 0x0b, 0x09, 0x04, + 0x0a, 0x08, 0x08, 0x04, 0x0a, 0x08, 0x09, 0x04, 0x0a, 0x09, 0x08, 0x04, + 0x0a, 0x09, 0x09, 0x04, 0x0b, 0x08, 0x08, 0x04, 0x0b, 0x08, 0x09, 0x04, + 0x0b, 0x09, 0x08, 0x04, 0x0b, 0x09, 0x09, 0x04, 0x0a, 0x0a, 0x08, 0x04, + 0x0a, 0x0a, 0x09, 0x04, 0x0a, 0x0b, 0x08, 0x04, 0x0a, 0x0b, 0x09, 0x04, + 0x0b, 0x0a, 0x08, 0x04, 0x0b, 0x0a, 0x09, 0x04, 0x0b, 0x0b, 0x08, 0x04, + 0x0b, 0x0b, 0x09, 0x04, 0x10, 0x10, 0x12, 0x05, 0x10, 0x10, 0x13, 0x05, + 0x10, 0x11, 0x12, 0x05, 0x10, 0x11, 0x13, 0x05, 0x11, 0x10, 0x12, 0x05, + 0x11, 0x10, 0x13, 0x05, 0x11, 0x11, 0x12, 0x05, 0x11, 0x11, 0x13, 0x05, + 0x10, 0x12, 0x12, 0x05, 0x10, 0x12, 0x13, 0x05, 0x10, 0x13, 0x12, 0x05, + 0x10, 0x13, 0x13, 0x05, 0x11, 0x12, 0x12, 0x05, 0x11, 0x12, 0x13, 0x05, + 0x11, 0x13, 0x12, 0x05, 0x11, 0x13, 0x13, 0x05, 0x12, 0x10, 0x12, 0x05, + 0x12, 0x10, 0x13, 0x05, 0x12, 0x11, 0x12, 0x05, 0x12, 0x11, 0x13, 0x05, + 0x13, 0x10, 0x12, 0x05, 0x13, 0x10, 0x13, 0x05, 0x13, 0x11, 0x12, 0x05, + 0x13, 0x11, 0x13, 0x05, 0x10, 0x14, 0x12, 0x05, 0x10, 0x14, 0x13, 0x05, + 0x10, 0x15, 0x12, 0x05, 0x10, 0x15, 0x13, 0x05, 0x11, 0x14, 0x12, 0x05, + 0x11, 0x14, 0x13, 0x05, 0x11, 0x15, 0x12, 0x05, 0x11, 0x15, 0x13, 0x05, + 0x14, 0x10, 0x10, 0x05, 0x14, 0x10, 0x11, 0x05, 0x14, 0x11, 0x10, 0x05, + 0x14, 0x11, 0x11, 0x05, 0x15, 0x10, 0x10, 0x05, 0x15, 0x10, 0x11, 0x05, + 0x15, 0x11, 0x10, 0x05, 0x15, 0x11, 0x11, 0x05, 0x14, 0x10, 0x12, 0x05, + 0x14, 0x10, 0x13, 0x05, 0x14, 0x11, 0x12, 0x05, 0x14, 0x11, 0x13, 0x05, + 0x15, 0x10, 0x12, 0x05, 0x15, 0x10, 0x13, 0x05, 0x15, 0x11, 0x12, 0x05, + 0x15, 0x11, 0x13, 0x05, 0x14, 0x12, 0x10, 0x05, 0x14, 0x12, 0x11, 0x05, + 0x14, 0x13, 0x10, 0x05, 0x14, 0x13, 0x11, 0x05, 0x15, 0x12, 0x10, 0x05, + 0x15, 0x12, 0x11, 0x05, 0x15, 0x13, 0x10, 0x05, 0x15, 0x13, 0x11, 0x05, + 0x14, 0x14, 0x10, 0x05, 0x14, 0x14, 0x11, 0x05, 0x14, 0x15, 0x10, 0x05, + 0x14, 0x15, 0x11, 0x05, 0x15, 0x14, 0x10, 0x05, 0x15, 0x14, 0x11, 0x05, + 0x15, 0x15, 0x10, 0x05, 0x15, 0x15, 0x11, 0x05, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x02, 0000, 0000, 0000, + 0x03, 0000, 0000, 0000, 0x04, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, + 0x06, 0000, 0000, 0000, 0x07, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x01, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x02, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x03, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x01, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0x02, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x04, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, 0x06, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x01, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x02, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x03, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x04, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x05, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x06, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x07, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x08, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x09, 0000, 0000, 0000, 0x0a, 0000, 0000, 0000, 0x0b, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0c, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x0d, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0x0e, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0x10, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x7f, 0x2c, 0x02, 0xbf, 0000, 0000, 0000, 0000, 0x56, 0xf9, 0x16, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x7f, 0x2c, 0x02, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x56, 0xf9, 0x16, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x58, 0xe4, 0x31, 0xbf, 0000, 0000, 0000, 0000, + 0x53, 0xbe, 0x4e, 0xbf, 0000, 0000, 0000, 0000, 0x53, 0xbe, 0x4e, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, + 0x0c, 0xe7, 0x74, 0x3e, 0000, 0000, 0000, 0000, 0x28, 0x1b, 0xa1, 0x3e, + 0000, 0000, 0000, 0000, 0x1d, 0x1b, 0xa1, 0x3e, 0000, 0000, 0000, 0000, + 0x9a, 0xa8, 0xe0, 0x3e, 0000, 0000, 0000, 0000, 0x3a, 0xcd, 0x13, 0xbf, + 0xf3, 0x04, 0x35, 0xbf, 0xf3, 0x04, 0x35, 0xbf, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xf3, 0x04, 0x35, 0xbf, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x3a, 0xcd, 0x13, 0xbf, 0xf3, 0x04, 0x35, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xf3, 0x04, 0x35, 0xbf, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xf3, 0x04, 0x35, 0xbf, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x3a, 0xcd, 0x13, 0xbf, 0000, 0000, 0000, 0000, 0xf3, 0x04, 0x35, 0xbf, + 0000, 0000, 0000, 0000, 0xf3, 0x04, 0x35, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0xf3, 0x04, 0x35, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xf3, 0x04, 0x35, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xff, 0x23, 0xbf, 0x3c, + 0x0b, 0x52, 0xe7, 0x3d, 0x3a, 0x51, 0xe7, 0x3d, 0x9b, 0xb6, 0x99, 0x3e, + 0x3a, 0x51, 0xe7, 0x3d, 0x9b, 0xb6, 0x99, 0x3e, 0x76, 0xb6, 0x99, 0x3e, + 0000, 0000, 0000, 0000, 0x3a, 0x51, 0xe7, 0x3d, 0xc0, 0xb6, 0x99, 0x3e, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xc0, 0xb6, 0x99, 0x3e, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x69, 0x50, 0xe7, 0x3d, 0x9b, 0xb6, 0x99, 0x3e, 0x76, 0xb6, 0x99, 0x3e, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xc0, 0xb6, 0x99, 0x3e, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xf4, 0x04, 0x35, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xf4, 0x04, 0x35, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xf3, 0x04, 0x35, 0xbf, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xf3, 0x04, 0x35, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x88, 0x94, 0x2f, 0xbe, 0000, 0000, 0000, 0000, + 0x1f, 0x2c, 0xbd, 0x3c, 0000, 0000, 0000, 0000, 0x1f, 0x2c, 0xbd, 0x3c, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x1f, 0x2c, 0xbd, 0x3c, 0000, 0000, 0000, 0000, 0x1f, 0x2c, 0xbd, 0x3c, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x1f, 0x2c, 0xbd, 0x3c, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x1f, 0x2c, 0xbd, 0x3c, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x1f, 0x2c, 0xbd, 0x3c, 0000, 0000, 0000, 0000, + 0x5b, 0x35, 0xbd, 0x3c, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0xef, 0x94, 0x2f, 0xbe, 0x80, 0x27, 0xbd, 0x3c, 0x80, 0x27, 0xbd, 0x3c, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x1f, 0x2c, 0xbd, 0x3c, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x80, 0x27, 0xbd, 0x3c, 0000, 0000, 0000, 0000, + 0x80, 0x27, 0xbd, 0x3c, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x80, 0x27, 0xbd, 0x3c, 0000, 0000, 0000, 0000, 0x5b, 0x35, 0xbd, 0x3c, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x00 +}; + +// this octree contains only one point, and the 2nd level is full, +// and key2xyz is false +static const unsigned char octree_5[] = { + 0x5f, 0x4f, 0x43, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x31, 0x2e, 0x30, 0x5f, + 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, + 0x01, 0000, 0000, 0000, 0x03, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x40, 0x40, 0000, 0000, 0xa0, 0x40, 0000, 0x01, 0000, 0000, + 0x01, 0000, 0000, 0000, 0x08, 0000, 0000, 0000, 0x08, 0000, 0000, 0000, + 0x08, 0000, 0000, 0000, 0x08, 0000, 0000, 0000, 0x08, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x09, 0000, 0000, 0000, 0x11, 0000, 0000, 0000, 0x19, 0000, 0000, 0000, + 0x21, 0000, 0000, 0000, 0x29, 0000, 0000, 0000, 0x29, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x01, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x01, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x3b, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x06, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x01, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, + 0x05, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0x40, 0000, 0000, 0000, 0x40, 0000, 0000, 0000, 0x40, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xcc, 0x02, 0000, 0000, 0x70, 0x03, 0000, 0000, + 0x14, 0x04, 0000, 0000, 0x14, 0x04, 0000, 0000, 0xd4, 0x04, 0000, 0000, + 0xf4, 0x04, 0000, 0000, 0x98, 0x05, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x02, 0000, 0000, 0000, + 0x03, 0000, 0000, 0000, 0x04, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, + 0x06, 0000, 0000, 0000, 0x07, 0000, 0000, 0000, 0x30, 0000, 0000, 0000, + 0x31, 0000, 0000, 0000, 0x32, 0000, 0000, 0000, 0x33, 0000, 0000, 0000, + 0x34, 0000, 0000, 0000, 0x35, 0000, 0000, 0000, 0x36, 0000, 0000, 0000, + 0x37, 0000, 0000, 0000, 0x80, 0x01, 0000, 0000, 0x81, 0x01, 0000, 0000, + 0x82, 0x01, 0000, 0000, 0x83, 0x01, 0000, 0000, 0x84, 0x01, 0000, 0000, + 0x85, 0x01, 0000, 0000, 0x86, 0x01, 0000, 0000, 0x87, 0x01, 0000, 0000, + 0000, 0x0c, 0000, 0000, 0x01, 0x0c, 0000, 0000, 0x02, 0x0c, 0000, 0000, + 0x03, 0x0c, 0000, 0000, 0x04, 0x0c, 0000, 0000, 0x05, 0x0c, 0000, 0000, + 0x06, 0x0c, 0000, 0000, 0x07, 0x0c, 0000, 0000, 0000, 0x60, 0000, 0000, + 0x01, 0x60, 0000, 0000, 0x02, 0x60, 0000, 0000, 0x03, 0x60, 0000, 0000, + 0x04, 0x60, 0000, 0000, 0x05, 0x60, 0000, 0000, 0x06, 0x60, 0000, 0000, + 0x07, 0x60, 0000, 0000, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x36, 0xcd, 0x13, 0x3f, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x40, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xc0, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0x40, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0x80, 0x3f, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0x3f, 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0x3f, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0x3f, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0x3f, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0x3f, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x00 +}; + +// this octree contains 3 points, and the 2nd level is full, +// and key2xyz is false +static const unsigned char octree_6[] = { + 0x5f, 0x4f, 0x43, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x31, 0x2e, 0x30, 0x5f, + 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, + 0x01, 0000, 0000, 0000, 0x03, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x40, 0x40, 0000, 0000, 0xa0, 0x40, 0000, 0x01, 0000, 0000, + 0x01, 0000, 0000, 0000, 0x08, 0000, 0000, 0000, 0x10, 0000, 0000, 0000, + 0x10, 0000, 0000, 0000, 0x10, 0000, 0000, 0000, 0x10, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x09, 0000, 0000, 0000, 0x19, 0000, 0000, 0000, 0x29, 0000, 0000, 0000, + 0x39, 0000, 0000, 0000, 0x49, 0000, 0000, 0000, 0x49, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x02, 0000, 0000, 0000, 0x02, 0000, 0000, 0000, 0x02, 0000, 0000, 0000, + 0x02, 0000, 0000, 0000, 0x03, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x3b, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x06, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x01, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, + 0x05, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0x40, 0000, 0000, 0000, 0x40, 0000, 0000, 0000, 0x40, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0xcc, 0x02, 0000, 0000, 0xf0, 0x03, 0000, 0000, + 0x14, 0x05, 0000, 0000, 0x14, 0x05, 0000, 0000, 0x94, 0x06, 0000, 0000, + 0xd4, 0x06, 0000, 0000, 0xf8, 0x07, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x02, 0000, 0000, 0000, + 0x03, 0000, 0000, 0000, 0x04, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, + 0x06, 0000, 0000, 0000, 0x07, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x01, 0000, 0000, 0000, 0x02, 0000, 0000, 0000, 0x03, 0000, 0000, 0000, + 0x04, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, 0x06, 0000, 0000, 0000, + 0x07, 0000, 0000, 0000, 0x30, 0000, 0000, 0000, 0x31, 0000, 0000, 0000, + 0x32, 0000, 0000, 0000, 0x33, 0000, 0000, 0000, 0x34, 0000, 0000, 0000, + 0x35, 0000, 0000, 0000, 0x36, 0000, 0000, 0000, 0x37, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, 0x02, 0000, 0000, 0000, + 0x03, 0000, 0000, 0000, 0x04, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, + 0x06, 0000, 0000, 0000, 0x07, 0000, 0000, 0000, 0x80, 0x01, 0000, 0000, + 0x81, 0x01, 0000, 0000, 0x82, 0x01, 0000, 0000, 0x83, 0x01, 0000, 0000, + 0x84, 0x01, 0000, 0000, 0x85, 0x01, 0000, 0000, 0x86, 0x01, 0000, 0000, + 0x87, 0x01, 0000, 0000, 0000, 0000, 0000, 0000, 0x01, 0000, 0000, 0000, + 0x02, 0000, 0000, 0000, 0x03, 0000, 0000, 0000, 0x04, 0000, 0000, 0000, + 0x05, 0000, 0000, 0000, 0x06, 0000, 0000, 0000, 0x07, 0000, 0000, 0000, + 0000, 0x0c, 0000, 0000, 0x01, 0x0c, 0000, 0000, 0x02, 0x0c, 0000, 0000, + 0x03, 0x0c, 0000, 0000, 0x04, 0x0c, 0000, 0000, 0x05, 0x0c, 0000, 0000, + 0x06, 0x0c, 0000, 0000, 0x07, 0x0c, 0000, 0000, 0000, 0000, 0000, 0000, + 0x01, 0000, 0000, 0000, 0x02, 0000, 0000, 0000, 0x03, 0000, 0000, 0000, + 0x04, 0000, 0000, 0000, 0x05, 0000, 0000, 0000, 0x06, 0000, 0000, 0000, + 0x07, 0000, 0000, 0000, 0000, 0x60, 0000, 0000, 0x01, 0x60, 0000, 0000, + 0x02, 0x60, 0000, 0000, 0x03, 0x60, 0000, 0000, 0x04, 0x60, 0000, 0000, + 0x05, 0x60, 0000, 0000, 0x06, 0x60, 0000, 0000, 0x07, 0x60, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x01, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x01, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x01, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0000, 0000, 0000, 0000, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, 0000, 0000, 0000, + 0xff, 0xff, 0xff, 0xff, 0000, 0000, 0x80, 0x3f, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0x3f, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0x36, 0xcd, 0x13, 0xbf, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x36, 0xcd, 0x13, 0x3f, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0x36, 0xcd, 0x13, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0x3f, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x40, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0x40, 0x40, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0xc0, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x40, 0xc0, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0000, 0x40, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0x80, 0xbf, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0x80, 0xbf, + 0000, 0000, 0000, 0x40, 0000, 0000, 0x80, 0xbf, 0000, 0000, 0x80, 0x3f, + 0000, 0000, 0x80, 0x3f, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0x3f, 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0x3f, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0x3f, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0x3f, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0x3f, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0x3f, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0x80, 0x3f, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0x3f, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0x3f, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0x80, 0x3f, 0000, 0000, 0000, 0000, 0x00 +}; + +const struct embedded_file { + const char *name; + const unsigned char *data; + size_t size; +} embedded_octrees[] = { + { "octree_1", octree_1, sizeof(octree_1) - 1 }, + { "octree_2", octree_2, sizeof(octree_2) - 1 }, + { "octree_3", octree_3, sizeof(octree_3) - 1 }, + { "octree_4", octree_4, sizeof(octree_4) - 1 }, + { "octree_5", octree_5, sizeof(octree_5) - 1 }, + { "octree_6", octree_6, sizeof(octree_6) - 1 }, + { nullptr, nullptr, 0 } +}; + +const unsigned char* get_one_octree(const char *name, size_t *size) { + const struct embedded_file *p = nullptr; + for (p = embedded_octrees; p->name != nullptr; p++) { + if (!strcmp(p->name, name)) { + if (size != nullptr) { *size = p->size; } + return p->data; + } + } + return nullptr; +} + +} // namespace octree \ No newline at end of file diff --git a/octree/octree/octree_samples.h b/octree/octree/octree_samples.h new file mode 100644 index 0000000..0e38283 --- /dev/null +++ b/octree/octree/octree_samples.h @@ -0,0 +1,12 @@ +#ifndef _OCTREE_OCTREE_SAMPLES_ +#define _OCTREE_OCTREE_SAMPLES_ + +#include + +namespace octree { + +const unsigned char* get_one_octree(const char *name, size_t *size = nullptr); + +} // namespace octree + +#endif // _OCTREE_OCTREE_SAMPLES_ \ No newline at end of file diff --git a/octree/octree/octree_value.cpp b/octree/octree/octree_value.cpp new file mode 100644 index 0000000..17012a1 --- /dev/null +++ b/octree/octree/octree_value.cpp @@ -0,0 +1,80 @@ +#include "octree_value.h" +#include "math_functions.h" + +#include + +pair OctreeValue::fval(const float x, const float y, const float z) const { + int oct_depth = octree_->info().depth(); + // If it is not an adaptive octree, then set the adp_depth as oct_depth + int adp_depth = octree_->info().is_adaptive() ? + octree_->info().adaptive_layer() : oct_depth; + + //pair + std::queue > node_stack; + node_stack.push(std::make_pair(0, 0)); + float phi = 0.0f, wt = 0.0f; + while (!node_stack.empty()) { + // pop + std::pair& node = node_stack.front(); + int id = node.first; + int depth = node.second; + int depth_child = depth + 1; + int id_child = octree_->children_cpu(depth)[id] * 8; + node_stack.pop(); + + // Rescale the input point into the range [1, 2^depth_child]^3 + float scale = 1.0f / float(1 << (oct_depth - depth_child)); + float pos_in[3] = { x * scale, y * scale, z * scale }; + + // Deal with the 8 children of the top node + for (int i = id_child; i < 8 + id_child; ++i) { + float pos_child[3], nm[3]; + octree_->node_pos(pos_child, i, depth_child); + float w = weight(pos_in, pos_child); + if (w != 0) { + if (octree_->children_cpu(depth_child)[i] < 0 || depth_child == oct_depth) { + // This node has no children + if (depth_child >= adp_depth) { + octree_->node_normal(nm, i, depth_child); + float len = abs(nm[0]) + abs(nm[1]) + abs(nm[2]); + if (len != 0) { + // This node has plane information + phi += w * basis(pos_in, pos_child, nm); + wt += w; + } + } + } else { + // This node has children, so push children + node_stack.push(std::make_pair(i, depth_child)); + } + } + } + } + if (wt < 1.0e-4f) wt = 0; + if (wt != 0) phi /= wt; + return std::make_pair(phi, wt); //pair +} + + +float OctreeValue::bspline2(float x) const { + if (x < -1.5f) { + return 0; + } else if (x < -0.5f) { + return 0.5f * (x + 1.5f) * (x + 1.5f); + } else if (x < 0.5f) { + return 0.75f - x * x; + } else if (x <= 1.5f) { + return 0.5f * (x - 1.5f) * (x - 1.5f); + } else { + return 0; + } +} + +inline float OctreeValue::basis(const float* pos, const float* c, const float* n) const { + return n[0] * (pos[0] - c[0]) + n[1] * (pos[1] - c[1]) + n[2] * (pos[2] - c[2]); +} + +inline float OctreeValue::weight(const float* pos, const float* c) const { + return bspline2(pos[0] - c[0]) * bspline2(pos[1] - c[1]) * bspline2(pos[2] - c[2]); +} + diff --git a/octree/octree/octree_value.h b/octree/octree/octree_value.h new file mode 100644 index 0000000..22e8753 --- /dev/null +++ b/octree/octree/octree_value.h @@ -0,0 +1,23 @@ +#ifndef _OCTREE_OCTREE_VALUE_ +#define _OCTREE_OCTREE_VALUE_ + +#include +#include "octree_parser.h" + +using std::pair; + +class OctreeValue { + public: + OctreeValue(const OctreeParser* oct = nullptr) : octree_(oct) {} + pair fval(const float x, const float y, const float z) const; + + protected: + float bspline2(float x) const; + inline float weight(const float* pos, const float* c) const; + inline float basis(const float* pos, const float* c, const float* n) const; + + protected: + const OctreeParser* octree_; +}; + +#endif // _OCTREE_OCTREE_VALUE_ diff --git a/octree/octree/points.cpp b/octree/octree/points.cpp new file mode 100644 index 0000000..f027ae2 --- /dev/null +++ b/octree/octree/points.cpp @@ -0,0 +1,204 @@ +#include "points.h" +#include "math_functions.h" + +#include +#include +#include +#include + +bool Points::read_points(const string& filename) { + std::ifstream infile(filename, std::ios::binary); + if (!infile) return false; + + infile.seekg(0, infile.end); + size_t len = infile.tellg(); + infile.seekg(0, infile.beg); + if (len < sizeof(PointsInfo)) { + // the file should at least contain a PtsInfo structure + infile.close(); + return false; + } + + buffer_.resize(len); + char* ptr_ = buffer_.data(); + infile.read(ptr_, len); + this->set(ptr_, (PointsInfo*)ptr_); + + infile.close(); + return true; +} + +bool Points::write_points(const string& filename) const { + std::ofstream outfile(filename, std::ios::binary); + if (!outfile) return false; + outfile.write(buffer_.data(), buffer_.size()); + outfile.close(); + return true; +} + +//bool Points::write_ply(const string & filename) const { +// std::ofstream outfile(filename, std::ios::binary); +// if (!outfile) return false; +// +// // write header +// int n = info_->pt_num(); +// outfile << "ply\nformat ascii 1.0\nelement vertex " << n +// << "\nproperty float x\nproperty float y\nproperty float z\n" +// << "property float nx\nproperty float ny\nproperty float nz\n" +// << "element face 0\nproperty list uchar int vertex_indices\n" +// << "end_header" << std::endl; +// +// // wirte contents +// const float* pts = ptr(PointsInfo::kPoint); +// const float* normals = ptr(PointsInfo::kNormal); +// if (normals == nullptr) { +// outfile.close(); +// return false; +// } + +bool Points::write_ply(const string & filename) const { + std::ofstream outfile(filename, std::ios::binary); + if (!outfile) return false; + + int n = info_->pt_num(); + const float* ptr_pts = points(); + const float* ptr_normal = normal(); + const float* ptr_feature = feature(); + const float* ptr_label = label(); + const int channel_pts = info_->channel(PointsInfo::kPoint); // 3 channel + const int channel_normal = info_->channel(PointsInfo::kNormal); // 3 channel + const int channel_feature = info_->channel(PointsInfo::kFeature); // x channel + const int channel_label = info_->channel(PointsInfo::kLabel); // 1 channel + // assert(channel_pts == 3 && channel_normal == 3 && channel_label == 1); + + // write header + std::ostringstream oss; + oss << "ply\nformat ascii 1.0\nelement vertex " << n + << "\nproperty float x\nproperty float y\nproperty float z\n"; + if (ptr_normal != nullptr) { + oss << "property float nx\nproperty float ny\nproperty float nz\n"; + } + if (ptr_feature != nullptr) { + for (int i = 0; i < channel_feature; ++i) { + oss << "property float feature" << i << "\n"; + } + } + if (ptr_label != nullptr) { + oss << "property float label\n"; + } + oss << "element face 0\nproperty list uchar int vertex_indices\nend_header\n"; + + // write content + for (int i = 0; i < n; ++i) { + for (int c = 0; c < channel_pts; ++c) { + oss << ptr_pts[i*channel_pts + c] << " "; + } + for (int c = 0; c < channel_normal; ++c) { + oss << ptr_normal[i*channel_normal + c] << " "; + } + for (int c = 0; c < channel_feature; ++c) { + oss << ptr_feature[i*channel_feature + c] << " "; + } + if (channel_label != 0) { + oss << ptr_label[i]; + } + oss << std::endl; + } + + // write to file + outfile << oss.str() << std::endl; + outfile.close(); + return true; +} + + +bool Points::set_points(const vector& pts, const vector& normals, + const vector& features, const vector& labels) { + /// set info + int num = pts.size() / 3; + // !!! Empty input is not allowed + if (num == 0) return false; + PointsInfo info; + info.set_pt_num(num); + info.set_channel(PointsInfo::kPoint, 3); + + if (!normals.empty()) { + int c = normals.size() / num; + int r = normals.size() % num; + // !!! The channel of normal has to be 3 + if (3 != c || 0 != r) return false; + info.set_channel(PointsInfo::kNormal, c); + } + + if (!features.empty()) { + int c = features.size() / num; + int r = features.size() % num; + // !!! The channel of feature has to larger than 0 + if (0 == c || 0 != r) return false; + info.set_channel(PointsInfo::kFeature, c); + } + + if (!labels.empty()) { + int c = labels.size() / num; + int r = labels.size() % num; + // !!! The channel of label has to be 1 + if (1 != c || 0 != r) return false; + info.set_channel(PointsInfo::kLabel, c); + } + + info.set_ptr_dis(); + + /// set buffer + int sz = info.sizeof_points(); + buffer_.resize(sz); + this->set(buffer_.data(), &info); // !!! remember to set the point parser !!! + std::copy(pts.begin(), pts.end(), mutable_ptr(PointsInfo::kPoint)); + if (!normals.empty()) { + std::copy(normals.begin(), normals.end(), mutable_ptr(PointsInfo::kNormal)); + } + if (!features.empty()) { + std::copy(features.begin(), features.end(), mutable_ptr(PointsInfo::kFeature)); + } + if (!labels.empty()) { + std::copy(labels.begin(), labels.end(), mutable_ptr(PointsInfo::kLabel)); + } + + return true; +} + +void Points::set_points(vector& data) { + buffer_.swap(data); + char* ptr_ = buffer_.data(); + this->set(ptr_, (PointsInfo*)ptr_); +} + +//void Points::set_bbox(float* bbmin, float* bbmax) { +// const int dim = 3; +// for (int i = 0; i < dim; ++i) { +// bbmin_[i] = bbmin[i]; +// bbmax_[i] = bbmax[i]; +// } +//} +// +//void Points::set_bbox() { +// const int dim = 3, npt = info_->pt_num(); +// const float* pt = mutable_ptr(PtsInfo::kPoint); +// if (npt < 1) { +// for (int c = 0; c < dim; ++c) { +// bbmin_[c] = bbmax_[c] = 0.0f; +// } +// return; +// } +// +// for (int c = 0; c < dim; ++c) { +// bbmin_[c] = bbmax_[c] = pt[c]; +// } +// for (int i = 1; i < npt; ++i) { +// int i3 = i * 3; +// for (int j = 0; j < dim; ++j) { +// float tmp = pt[i3 + j]; +// if (tmp < bbmin_[j]) bbmin_[j] = tmp; +// if (tmp > bbmax_[j]) bbmax_[j] = tmp; +// } +// } +//} diff --git a/octree/octree/points.h b/octree/octree/points.h new file mode 100644 index 0000000..98d87ff --- /dev/null +++ b/octree/octree/points.h @@ -0,0 +1,31 @@ +#ifndef _OCTREE_POINTS_ +#define _OCTREE_POINTS_ + +#include +#include +#include "points_parser.h" + +using std::vector; +using std::string; + +class Points: public PointsParser { + public: + Points() : buffer_() {} + + // the pts must not be empty, the labels may be empty, + // the normals & features must not be empty at the same time. + bool set_points(const vector& pts, const vector& normals, + const vector& features = vector(), + const vector& labels = vector()); + void set_points(vector& data); // swap data and buffer_ + + bool read_points(const string& filename); + bool write_points(const string& filename) const; + bool write_ply(const string& filename) const; + + protected: + vector buffer_; +}; + + +#endif // _OCTREE_POINTS_ diff --git a/octree/octree/points_info.cpp b/octree/octree/points_info.cpp new file mode 100644 index 0000000..4db706d --- /dev/null +++ b/octree/octree/points_info.cpp @@ -0,0 +1,61 @@ +#include "points_info.h" +#include + +const char PointsInfo::kMagicStr[16] = "_POINTS_1.0_"; + +void PointsInfo::reset() { + memset(this, 0, sizeof(PointsInfo)); + strcpy(magic_str_, kMagicStr); +} + +bool PointsInfo::check_format(string& msg) const { + msg.clear(); + if (strcmp(kMagicStr, magic_str_) != 0) { + msg += "The version of points format is not " + string(kMagicStr) + ".\n"; + } + if (pt_num_ <= 0) { + msg += "The pt_num_ should be larger than 0.\n"; + } + // todo: add more checks + + // the PtsInfo is valid when no error message is produced + return msg.empty(); +} + +int PointsInfo::channel(PropType ptype) const { + int i = property_index(ptype); + if (!has_property(ptype)) return 0; + return channels_[i]; +} + +void PointsInfo::set_channel(PropType ptype, const int ch) { + // note: the channel and content_flags_ are consisent. + // If channels_[i] != 0, then the i^th bit of content_flags_ is 1. + int i = property_index(ptype); + channels_[i] = ch; + content_flags_ |= ptype; +} + +int PointsInfo::ptr_dis(PropType ptype) const { + int i = property_index(ptype); + if (!has_property(ptype)) return -1; + return ptr_dis_[i]; +} + +void PointsInfo::set_ptr_dis() { + // the accumulated pointer displacement + ptr_dis_[0] = sizeof(PointsInfo); + for (int i = 1; i <= kPTypeNum; ++i) { // note the " <= " is used here + ptr_dis_[i] = ptr_dis_[i - 1] + sizeof(float) * pt_num_ * channels_[i - 1]; + } +} + +int PointsInfo::property_index(PropType ptype) const { + int k = 0, p = ptype; + for (int i = 0; i < kPTypeNum; ++i) { + if (0 != (p & (1 << i))) { + k = i; break; + } + } + return k; +} diff --git a/octree/octree/points_info.h b/octree/octree/points_info.h new file mode 100644 index 0000000..b67a5b9 --- /dev/null +++ b/octree/octree/points_info.h @@ -0,0 +1,42 @@ +#ifndef _OCTREE_POINTS_INFO_ +#define _OCTREE_POINTS_INFO_ + +#include + +using std::string; + +class PointsInfo { + public: + enum PropType { kPoint = 1, kNormal = 2, kFeature = 4, kLabel = 8 }; + static const int kPTypeNum = 4; + static const char kMagicStr[16]; + + public: + PointsInfo() { reset(); } + void reset(); + bool check_format(string& msg) const; + bool has_property(PropType ptype) const { + return (content_flags_ & ptype) != 0; + } + + int pt_num() const { return pt_num_; } + int channel(PropType ptype) const; + int ptr_dis(PropType ptype) const; + int sizeof_points() const { return ptr_dis_[kPTypeNum]; } + + void set_pt_num(int num) { pt_num_ = num; } + void set_channel(PropType ptype, const int ch); + void set_ptr_dis(); + + protected: + int property_index(PropType ptype) const; + + protected: + char magic_str_[16]; + int pt_num_; + int content_flags_; + int channels_[8]; + int ptr_dis_[8]; +}; + +#endif // _OCTREE_POINTS_INFO_ diff --git a/octree/octree/points_parser.cpp b/octree/octree/points_parser.cpp new file mode 100644 index 0000000..d26a05e --- /dev/null +++ b/octree/octree/points_parser.cpp @@ -0,0 +1,218 @@ +#include "points_parser.h" +#include "math_functions.h" + +#include +#include +#include +#include + + +void PointsParser::set(const void* ptr) { + const_ptr_ = true; + metadata_ = reinterpret_cast(const_cast(ptr)); + info_ = reinterpret_cast(metadata_); +} + +void PointsParser::set(void* ptr, PointsInfo* ptsinfo) { + const_ptr_ = false; + metadata_ = reinterpret_cast(ptr); + info_ = reinterpret_cast(ptr); + if (ptsinfo != nullptr) { // update the OctreeInfo with octinfo + memcpy(info_, ptsinfo, sizeof(PointsInfo)); + } +} + +bool PointsParser::is_empty() const { + return info_ == nullptr || info_->pt_num() == 0; +} + +const float* PointsParser::ptr(PointsInfo::PropType ptype) const { + const float* p = nullptr; + int dis = info_->ptr_dis(ptype); + if (-1 != dis) { + p = reinterpret_cast(metadata_ + dis); + } + return p; +} + +float* PointsParser::mutable_ptr(PointsInfo::PropType ptype) { + return const_cast(ptr(ptype)); +} + +void PointsParser::translate(const float* center) { + const int dim = 3, npt = info_->pt_num(); + float* pt = mutable_points(); + for (int i = 0; i < npt; ++i) { + int i3 = i * 3; + for (int m = 0; m < dim; ++m) { + pt[i3 + m] += center[m]; + } + } +} + +void PointsParser::displace(const float dis) { + const int dim = 3, npt = info_->pt_num(); + float* pt = mutable_points(); + float* normal = mutable_normal(); + if (normal == nullptr) return; + + for (int i = 0; i < npt; ++i) { + int i3 = i * 3; + for (int m = 0; m < 3; ++m) { + pt[i3 + m] += normal[i3 + m] * dis; + } + } +} + +void PointsParser::uniform_scale(const float s) { + const int npt = info_->pt_num(); + float* pt = mutable_points(); + for (int i = 0; i < 3 * npt; ++i) { + pt[i] *= s; + } +} + +void PointsParser::scale(const float* s) { + if (s[0] == 1.0f && s[1] == 1.0f && s[2] == 1.0f) { return; } + + int npt = info_->pt_num(); + float* pt = this->mutable_points(); + for (int i = 0; i < npt; ++i) { + int ix3 = i * 3; + for (int j = 0; j < 3; ++j) { + pt[ix3 + j] *= s[j]; + } + } + + if (this->info().has_property(PointsInfo::kNormal)) { + float* nm = this->mutable_normal(); + for (int i = 0; i < npt; ++i) { + int ix3 = i * 3; + for (int j = 0; j < 3; ++j) { + nm[ix3 + j] *= s[j]; + } + } + normalize_nx3(nm, npt); + } +} + +void PointsParser::rotate(const float angle, const float* axis) { + float rot[9]; + rotation_matrix(rot, angle, axis); + + int npt = info_->pt_num(); + vector tmp(3 * npt); + matrix_prod(tmp.data(), rot, mutable_points(), 3, npt, 3); + std::copy(tmp.begin(), tmp.end(), mutable_points()); + + if (this->info().has_property(PointsInfo::kNormal)) { + matrix_prod(tmp.data(), rot, this->mutable_normal(), 3, npt, 3); + std::copy(tmp.begin(), tmp.end(), mutable_normal()); + } +} + +void PointsParser::rotate(const float* angles) { + float rot[9]; + rotation_matrix(rot, angles); + + int npt = info_->pt_num(); + vector tmp(3 * npt); + matrix_prod(tmp.data(), rot, mutable_points(), 3, npt, 3); + std::copy(tmp.begin(), tmp.end(), mutable_points()); + + if (this->info().has_property(PointsInfo::kNormal)) { + matrix_prod(tmp.data(), rot, this->mutable_normal(), 3, npt, 3); + std::copy(tmp.begin(), tmp.end(), mutable_normal()); + } +} + +void PointsParser::transform(const float* mat) { + int npt = info_->pt_num(); + vector tmp(3 * npt); + matrix_prod(tmp.data(), mat, mutable_points(), 3, npt, 3); + std::copy(tmp.begin(), tmp.end(), mutable_points()); + + if (this->info().has_property(PointsInfo::kNormal)) { + float mat_it[9]; + inverse_transpose_3x3(mat_it, mat); + matrix_prod(tmp.data(), mat_it, mutable_normal(), 3, npt, 3); + bool is_unitary = almost_equal_3x3(mat_it, mat); + + if (!is_unitary) { + normalize_nx3(tmp.data(), npt); + } + + std::copy(tmp.begin(), tmp.end(), mutable_normal()); + } +} + +void PointsParser::clip(const float* bbmin, const float* bbmax) { + int npt = info_->pt_num(), npt_in_bbox = 0; + float* pts = mutable_points(); + vector in_bbox(npt, 0); + for (int i = 0; i < npt; ++i) { + int ix3 = i * 3; + in_bbox[i] = bbmin[0] < pts[ix3] && pts[ix3] < bbmax[0] && + bbmin[1] < pts[ix3 + 1] && pts[ix3 + 1] < bbmax[1] && + bbmin[2] < pts[ix3 + 2] && pts[ix3 + 2] < bbmax[2]; + npt_in_bbox += in_bbox[i]; + } + + if (npt_in_bbox == npt) return; // early stop + if (npt_in_bbox == 0) { // no points + // just keep one point to avoid the degenerated case + npt_in_bbox = 1; + in_bbox[0] = 1; + float* p = mutable_points(); + for (int i = 0; i < 3; ++i) { p[i] = bbmin[i]; } + } + + // Just discard the points which are out of the bbox + for (int t = 0; t < PointsInfo::kPTypeNum; ++t) { + auto ptype = static_cast(1 << t); + int channel = info_->channel(ptype); + if (channel == 0) { continue; } + float* p = mutable_ptr(ptype); + for (int i = 0, j = 0; i < npt; ++i) { + if (in_bbox[i] == 0) continue; + int ixC = i * channel, jxC = j * channel; + for (int c = 0; c < channel; ++c) { + p[jxC + c] = p[ixC + c]; + } + j++; + } + } + + info_->set_pt_num(npt_in_bbox); +} + +void PointsParser::add_noise(const float std_pt, const float std_nm) { + unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); + std::default_random_engine generator(seed); + std::normal_distribution dis_pt(0.0f, std_pt), dis_nm(0.0f, std_nm); + + int npt = info_->pt_num(); + if (std_pt > 1.0e-5f) { + float* pt = mutable_ptr(PointsInfo::kPoint); + for (int i = 0; i < 3 * npt; ++i) { + pt[i] += dis_pt(generator); + } + } + + if (std_nm > 1.0e-5f && this->info().has_property(PointsInfo::kNormal)) { + float* nm = mutable_normal(); + for (int i = 0; i < 3 * npt; ++i) { + nm[i] += dis_nm(generator); + } + normalize_nx3(nm, npt); + } +} + +void PointsParser::normalize() { + float radius, center[3], trans[3]; + bounding_sphere(radius, center, this->points(), this->info().pt_num()); + for (int i = 0; i < 3; ++i) { trans[i] = -center[i]; } + + this->translate(trans); + this->uniform_scale(1 / (radius + 1.0e-10f)); +} diff --git a/octree/octree/points_parser.h b/octree/octree/points_parser.h new file mode 100644 index 0000000..3c9e64c --- /dev/null +++ b/octree/octree/points_parser.h @@ -0,0 +1,57 @@ +#ifndef _OCTREE_POINTS_PARSER_ +#define _OCTREE_POINTS_PARSER_ + +#include +#include +#include "points_info.h" + +using std::vector; +using std::string; + +class PointsParser { + public: + PointsParser() : metadata_(nullptr), info_(nullptr), const_ptr_(true) {} + void set(const void* ptr); + void set(void* ptr, PointsInfo* ptsinfo = nullptr); + bool is_empty() const; + + const char* data() const { return metadata_; } + const PointsInfo& info() const { return *info_; } + PointsInfo& mutable_info() { return *info_; } + + const float* ptr(PointsInfo::PropType ptype) const; + float* mutable_ptr(PointsInfo::PropType ptype); + + const float* points() const { return ptr(PointsInfo::kPoint); } + const float* normal() const { return ptr(PointsInfo::kNormal); } + const float* feature() const { return ptr(PointsInfo::kFeature); } + const float* label() const { return ptr(PointsInfo::kLabel); } + float* mutable_points() { return mutable_ptr(PointsInfo::kPoint); } + float* mutable_normal() { return mutable_ptr(PointsInfo::kNormal); } + float* mutable_feature() { return mutable_ptr(PointsInfo::kFeature); } + float* mutable_label() { return mutable_ptr(PointsInfo::kLabel); } + + // todo: move the following functions out of this class (to "transform_points.h") + void translate(const float* center); + void displace(const float dis); + void uniform_scale(const float s); + void scale(const float* s); + void rotate(const float angle, const float* axis); // angle in radian + void rotate(const float* angles); + void transform(const float* trans_matrix); + void clip(const float* bbmin, const float* bbmax); + void add_noise(const float std_pt, const float std_nm); + void normalize(); // translate and scale the points to unit sphere + + + protected: + char* metadata_; + PointsInfo* info_; + bool const_ptr_; + + private: + PointsInfo info_buffer_; +}; + + +#endif // _OCTREE_POINTS_PARSER_ diff --git a/octree/octree/simplify_points.cpp b/octree/octree/simplify_points.cpp new file mode 100644 index 0000000..386976a --- /dev/null +++ b/octree/octree/simplify_points.cpp @@ -0,0 +1,139 @@ +#include "simplify_points.h" +#include "math_functions.h" + + +SimplifyPoints::SimplifyPoints(int dim, bool avg_points, float offset) { + dim_ = dim; + offset_ = offset; + avg_points_ = avg_points; + spatial_hash_.resize(dim_ * dim_ * dim_); +} + +string SimplifyPoints::set_point_cloud(const string filename) { + // load point cloud + string error_msg; + bool succ = point_cloud_.read_points(filename); + if (!succ) { + error_msg = "Can not load " + filename; + return error_msg; + } + succ = point_cloud_.info().check_format(error_msg); + if (!succ) { + error_msg = filename + ": " + error_msg; + return error_msg; + } + + // deal with empty points + int npt = point_cloud_.info().pt_num(); + if (npt == 0) { + error_msg = filename + ": This is an empty points!"; + return error_msg; + } + return error_msg; +} + +bool SimplifyPoints::write_point_cloud(const string filename) { + return point_cloud_.write_points(filename); +} + +void SimplifyPoints::simplify() { + // init + transform(); + spatial_hash_.assign(dim_ * dim_ * dim_, -1); + vector normal_output, pts_output; + vector pt_num; + + // average + const float* pts = point_cloud_.ptr(PointsInfo::kPoint); + const float* normals = point_cloud_.ptr(PointsInfo::kNormal); + int nnum = point_cloud_.info().pt_num(); + for (int i = 0, id = 0; i < nnum; ++i) { + // hash + int ix3 = i * 3; + int x = static_cast(pts[ix3]); + int y = static_cast(pts[ix3 + 1]); + int z = static_cast(pts[ix3 + 2]); + int h = (x * dim_ + y) * dim_ + z; + + if (spatial_hash_[h] == -1) { + spatial_hash_[h] = id++; + + pt_num.push_back(0); + for (int c = 0; c < 3; ++c) { + pts_output.push_back(0.0f); + normal_output.push_back(0.0f); + } + } + + int j = spatial_hash_[h]; + int jx3 = j * 3; + pt_num[j] += 1; + + if (avg_points_) { + for (int c = 0; c < 3; ++c) { + normal_output[jx3 + c] += normals[ix3 + c]; + pts_output[jx3 + c] += pts[ix3 + c]; + } + } else { + for (int c = 0; c < 3; ++c) { + normal_output[jx3 + c] = normals[ix3 + c]; + pts_output[jx3 + c] = pts[ix3 + c]; + } + } + } + + // normalize + if (avg_points_) { + int n = pt_num.size(); + for (int i = 0; i < n; ++i) { + int ix3 = i * 3; + float len = norm2(normal_output.data() + ix3, 3) + 1.0e-20f; + for (int c = 0; c < 3; ++c) { + normal_output[ix3 + c] /= len; + pts_output[ix3 + c] /= pt_num[i]; + } + } + } + + point_cloud_.set_points(pts_output, normal_output); + inv_transform(); +} + +void SimplifyPoints::transform() { + // bounding sphere + int npt = point_cloud_.info().pt_num(); + bounding_sphere(radius_, center_, point_cloud_.ptr(PointsInfo::kPoint), npt); + + // centralize & displacement + radius_ *= 1.001f; + if (offset_ > 1.0e-10f) { + offest_obj_ = offset_ * 2.0f * radius_ / float(dim_); + point_cloud_.displace(offest_obj_); + radius_ += offest_obj_ * 1.001f; + } + float origin[3] = { + radius_ - center_[0], radius_ - center_[1], radius_ - center_[2] + }; + point_cloud_.translate(origin); // translate the points to origin + + + // scale + float mul = dim_ / (2.0f * radius_); + point_cloud_.uniform_scale(mul); +} + +void SimplifyPoints::inv_transform() { + // scale + float mul = 2.0f * radius_ / dim_; + point_cloud_.uniform_scale(mul); + + // translate & displacement + float origin[3] = { + center_[0] - radius_, center_[1] - radius_, center_[2] - radius_ + }; + point_cloud_.translate(origin); + if (offset_ > 1.0e-10f) { + point_cloud_.displace(-offest_obj_); + } + +} diff --git a/octree/octree/simplify_points.h b/octree/octree/simplify_points.h new file mode 100644 index 0000000..c7388e9 --- /dev/null +++ b/octree/octree/simplify_points.h @@ -0,0 +1,32 @@ +#ifndef _OCTREE_SIMPLIFY_POINTS_ +#define _OCTREE_SIMPLIFY_POINTS_ + +#include +#include "points.h" + +using std::vector; +using std::string; + +class SimplifyPoints { + public: + SimplifyPoints(int dim, bool avg_points, float offset); + string set_point_cloud(const string filename); + bool write_point_cloud(const string filename); + void simplify(); + + protected: + void transform(); + void inv_transform(); + + protected: + Points point_cloud_; + float radius_, center_[3]; + + int dim_; + float offset_; + float offest_obj_; + bool avg_points_; + vector spatial_hash_; +}; + +#endif // _OCTREE_SIMPLIFY_POINTS_ diff --git a/octree/octree/transform_octree.cpp b/octree/octree/transform_octree.cpp new file mode 100644 index 0000000..9c8ec17 --- /dev/null +++ b/octree/octree/transform_octree.cpp @@ -0,0 +1,428 @@ +#include "transform_octree.h" +#include "math_functions.h" +#include +#include +#include +#include + +void ScanOctree::set_scale(float scale) { + scale_ = scale; +} + +void ScanOctree::set_axis(const float* axis, int n) { + if (n == 3) { + for (int i = 0; i < 3; ++i) { z_[i] = axis[i]; } + axes(x_, y_, z_); + } else { // n == 9 + x_[0] = axis[0]; x_[1] = axis[1]; x_[2] = axis[2]; + y_[0] = axis[3]; y_[1] = axis[4]; y_[2] = axis[5]; + z_[0] = axis[6]; z_[1] = axis[7]; z_[2] = axis[8]; + } +} + +void ScanOctree::scan(vector& octree_out, const OctreeParser& octree_in, + const vector& axis) { + // drop_flags: 1 - drop; 0 - keep, iff the node is visible and non-empty + int depth = octree_in.info().depth(); + int num = octree_in.info().node_num(depth); + vector > drop_flags(depth + 1); + drop_flags[depth].resize(num, 1); + + const int axis_channel = 3; + int axis_num = axis.size() / axis_channel; + for (int i = 0; i < axis_num; ++i) { + // set the scanning coordinate system + // if rot_channel == 3, then it is an axis, i.e. only z axis + // if rot_channel == 9, then it is an rotation matrix + set_axis(axis.data() + i * axis_channel, axis_channel); + + // calc the bound of the x-y projection plane + bbox_xy(depth); + + // run the z_buffer algorithm + vector flags(num, 1); + z_buffer(flags, octree_in); + for (int j = 0; j < num; ++j) { + if (flags[j] == 0) drop_flags[depth][j] = 0; + } + } + + // generate drop flags for other octree layers + generate_flags(drop_flags, octree_in); + + // drop tree according to the flags + trim_octree(octree_out, octree_in, drop_flags); +} + + +void ScanOctree::bbox_xy(int depth) { + for (int i = 0; i < 3; ++i) { + bbmin_[i] = std::numeric_limits::max(); + bbmax_[i] = -std::numeric_limits::max(); + } + + for (int i = 0; i < 8; ++i) { + float pt[3] = { i / 4, (i / 2) % 2, i % 2 }; + float x = dot_prod(pt, x_); + if (x < bbmin_[0]) bbmin_[0] = x; + if (x > bbmax_[0]) bbmax_[0] = x; + + float y = dot_prod(pt, y_); + if (y < bbmin_[1]) bbmin_[1] = y; + if (y > bbmax_[1]) bbmax_[1] = y; + + float z = dot_prod(pt, z_); + if (z < bbmin_[2]) bbmin_[2] = z; + if (z > bbmax_[2])bbmax_[2] = z; + } + + float width = -1; + float scale = 1 << depth; + for (int i = 0; i < 3; ++i) { + bbmax_[i] *= scale; + bbmin_[i] *= scale; + + float dis = bbmax_[i] - bbmin_[i]; + if (dis > width) width = dis; + } + + width_ = static_cast(width * scale_) + 2; // slightly larger +} + +void ScanOctree::reset_buffer() { + id_buffer_.assign(width_ * width_, -1); + z_buffer_.assign(width_ * width_, std::numeric_limits::max()); +} + +void ScanOctree::z_buffer(vector& drop_flags, const OctreeParser& octree_in) { + // reset the buffer + reset_buffer(); + + const int depth = octree_in.info().depth(); + const int num = octree_in.info().node_num(depth); + + const unsigned int* key = octree_in.key_cpu(depth); + const int* children = octree_in.children_cpu(depth); + for (int i = 0; i < num; ++i) { + if (children[i] < 0) continue; // empty node + + float normal[3]; + octree_in.node_normal(normal, i, depth); + if (dot_prod(normal, z_) >= 0) continue; // invisible node + + float pt[3]; + octree_in.key2xyz(pt, key[i], depth); + float x = (dot_prod(x_, pt) - bbmin_[0]) * scale_; + float y = (dot_prod(y_, pt) - bbmin_[1]) * scale_; + float z = dot_prod(z_, pt); + + int k = static_cast(x + 0.5f) * width_ + static_cast(y + 0.5f); + if (z_buffer_[k] > z) { + z_buffer_[k] = z; + int id = id_buffer_[k]; + if (id >= 0) { + drop_flags[id] = 1; + } + id_buffer_[k] = i; + drop_flags[i] = 0; + } + } +} + +void ScanOctree::generate_flags(vector>& drop_flags, + const OctreeParser& octree_in) { + int depth = octree_in.info().depth(); + int depth_full = octree_in.info().full_layer(); + for (int d = 0; d < depth_full; ++d) { + // keep all the nodes whose depth are smaller than depth_full + drop_flags[d].assign(1 << (3 * d), 0); + } + for (int d = depth - 1; d >= depth_full; --d) { + int num = octree_in.info().node_num(d); + drop_flags[d].assign(num, 1); + const int* child_d = octree_in.children_cpu(d); + for (int i = 0; i < num; ++i) { + int j = child_d[i]; + if (j < 0) continue; // empty node + + int drop = 1; + for (int k = j * 8; k < j * 8 + 8; ++k) { + // keep the node if any of its children nodes is kept + if (drop_flags[d + 1][k] == 0) { + drop = 0; + break; + } + } + drop_flags[d][i] = drop; + } + } +} + +void ScanOctree::trim_octree(vector& octree_buffer, + const OctreeParser& octree_in, vector>& drop_flags) { + // calculate the node number for the octree_out + int depth = octree_in.info().depth(); + int depth_full = octree_in.info().full_layer(); + vector node_num_nempty(depth + 1, 0); + for (int d = 0; d < depth_full; ++d) { + node_num_nempty[d] = 1 << (3 * d); + } + for (int d = depth_full; d <= depth; ++d) { + int num = 0; + for (auto v : drop_flags[d]) { + if (v == 0) num++; + } + node_num_nempty[d] = num; + } + vector node_num(depth + 1, 0); + node_num[0] = 1; + for (int d = 1; d <= depth; ++d) { + node_num[d] = 8 * node_num_nempty[d - 1]; + } + + // initialize + OctreeInfo info_out = octree_in.info(); + info_out.set_nnum(node_num.data()); + info_out.set_nempty(node_num_nempty.data()); + info_out.set_nnum_cum(); + info_out.set_ptr_dis(); + octree_buffer.resize(info_out.sizeof_octree()); + OctreeParser octree_out; + octree_out.set_cpu(octree_buffer.data(), &info_out); + + // copy data + // !!! current channel_key = 1 + int channel_feature = octree_in.info().channel(OctreeInfo::kFeature); + int location_feature = octree_in.info().locations(OctreeInfo::kFeature); + int channel_label = octree_in.info().channel(OctreeInfo::kLabel); + int location_label = octree_in.info().locations(OctreeInfo::kLabel); + int channel_split = octree_in.info().channel(OctreeInfo::kSplit); + int location_split = octree_in.info().locations(OctreeInfo::kSplit); + + for (int d = 1; d <= depth; ++d) { + int num = octree_in.info().node_num(d - 1); + vector& drop = drop_flags[d - 1]; + vector& drop_d = drop_flags[d]; + + // copy children and key + // !!! Caveat: currently channel_key is 1, and channel_child is 1 + const int* child_in = octree_in.children_cpu(d - 1); + const unsigned int* key_in = octree_in.key_cpu(d); + int* child_out = octree_out.mutable_children_cpu(d); + unsigned int* key_out = octree_out.mutable_key_cpu(d); + for (int i = 0, j = 0, id = 0; i < num; ++i) { + // the node is dropped or empty + if (drop[i] == 1) continue; + int t = child_in[i]; + + for (int k = 8 * t; k < 8 * t + 8; ++k) { + key_out[j] = key_in[k]; + + // the node is non-empty and kept + int ch = drop_d[k] == 0 ? id++ : -1; + child_out[j] = ch; + + j++; + } + } + + // copy feature + if (location_feature == -1 || d == depth) { + int nnum_in = octree_in.info().node_num(d); + int nnum_out = octree_out.info().node_num(d); + const float * feature_in = octree_in.feature_cpu(d); + float* feature_out = octree_out.mutable_feature_cpu(d); + for (int i = 0, j = 0; i < num; ++i) { + // the node is dropped or empty + if (drop[i] == 1) continue; + int t = child_in[i]; + + for (int k = 8 * t; k < 8 * t + 8; ++k) { + for (int c = 0; c < channel_feature; ++c) { + feature_out[c * nnum_out + j] = drop_d[k] == 0 ? + feature_in[c * nnum_in + k] : 0; + } + j++; + } + } + } + + // copy label + if ((location_label == -1 || d == depth) && channel_label != 0) { + const float * label_in = octree_in.label_cpu(d); + float* label_out = octree_out.mutable_label_cpu(d); + for (int i = 0, j = 0; i < num; ++i) { + // the node is dropped or empty + if (drop[i] == 1) continue; + int t = child_in[i]; + + for (int k = 8 * t; k < 8 * t + 8; ++k) { + label_out[j] = drop_d[k] == 0 ? label_in[k] : -1; + ++j; + } + } + } + + // copy split + if ((location_split == -1 || d == depth) && channel_split != 0) { + const float * split_in = octree_in.split_cpu(d); + float* split_out = octree_out.mutable_split_cpu(d); + for (int i = 0, j = 0; i < num; ++i) { + // the node is dropped or empty + if (drop[i] == 1) continue; + int t = child_in[i]; + + for (int k = 8 * t; k < 8 * t + 8; ++k) { + split_out[j] = drop_d[k] == 0 ? split_in[k] : 0; + ++j; + } + } + } + } +} + + + +void octree_dropout(vector& octree_output, const string& octree_input, + const int depth_dropout, const float threshold) { + // generate the drop flag + OctreeParser parser_in; + parser_in.set_cpu(octree_input.c_str()); + int depth = parser_in.info().depth(); + vector > drop(depth + 1); + // generate random flag for the level depth_dropout + int nnum_d = parser_in.info().node_num(depth_dropout); + drop[depth_dropout].resize(nnum_d, 0); + std::default_random_engine generator(static_cast(time(nullptr))); + std::bernoulli_distribution distribution(threshold); + for (int i = 0; i < nnum_d; ++i) { + drop[depth_dropout][i] = static_cast(distribution(generator)); + } + for (int d = depth_dropout + 1; d <= depth; ++d) { + int nnum_d = parser_in.info().node_num(d); + int nnum_dp = parser_in.info().node_num(d - 1); + const int* children_dp = parser_in.children_cpu(d - 1); + drop[d].resize(nnum_d); + for (int i = 0; i < nnum_dp; ++i) { + int t = children_dp[i]; + if (t < 0) continue; // continue if it has no children + // assign the drop flag of a parent node to its children + for (int j = 0; j < 8; ++j) { + drop[d][t * 8 + j] = drop[d - 1][i]; + } + } + } + + // init output + OctreeInfo info_output = parser_in.info(); + vector node_num(depth + 1, 0); + for (int d = 0; d <= depth; ++d) { + if (d <= depth_dropout) { + node_num[d] = parser_in.info().node_num(d); + } else { + int num = 0; + for (auto v : drop[d]) { + if (v == 0) num++; + } + node_num[d] = num; + } + } + info_output.set_nnum(node_num.data()); + info_output.set_nnum_cum(); + info_output.set_ptr_dis(); + octree_output.resize(info_output.sizeof_octree()); + OctreeParser parser_out; + parser_out.set_cpu(octree_output.data(), &info_output); + + // start dropout + // from level 0 to depth_output + int num = parser_in.info().node_num_cum(depth_dropout + 1); + int channel_key = parser_in.info().channel(OctreeInfo::kKey); + //CHECK_EQ(channel_key, 1) << "Currently the channel must be 1"; + std::copy_n(parser_in.key_cpu(0), num * channel_key, parser_out.mutable_key_cpu(0)); + std::copy_n(parser_in.children_cpu(0), num, parser_out.mutable_children_cpu(0)); + int channel_feature = parser_in.info().channel(OctreeInfo::kFeature); + int location_feature = parser_in.info().locations(OctreeInfo::kFeature); + if (location_feature == -1) { + std::copy_n(parser_in.feature_cpu(0), num * channel_feature, parser_out.mutable_feature_cpu(0)); + } + int channel_label = parser_in.info().channel(OctreeInfo::kLabel); + int location_label = parser_in.info().locations(OctreeInfo::kLabel); + if (location_label == -1) { + std::copy_n(parser_in.label_cpu(0), num * channel_label, parser_out.mutable_label_cpu(0)); + } + int channel_split = parser_in.info().channel(OctreeInfo::kSplit); + int location_split = parser_in.info().locations(OctreeInfo::kSplit); + if (location_split == -1) { + std::copy_n(parser_in.split_cpu(0), num * channel_split, parser_out.mutable_split_cpu(0)); + } + + // from level depth_output+1 to depth + vector node_num_nempty(depth + 1, 0); + for (int d = depth_dropout + 1; d <= depth; ++d) { + int nnum_d = parser_in.info().node_num(d), id = 0; + const int* child_src = parser_in.children_cpu(d); + const unsigned int* key_src = parser_in.key_cpu(d); + int* child_des = parser_out.mutable_children_cpu(d); + unsigned int* key_des = parser_out.mutable_key_cpu(d); + for (int i = 0, j = 0; i < nnum_d; ++i) { + if (drop[d][i] == 0) { + key_des[j] = key_src[i]; + int ch = child_src[i] < 0 ? child_src[i] : id++; + child_des[j] = ch; + ++j; + } + } + node_num_nempty[d] = id; + + if (location_feature == -1 || d == depth) { + int nnum_src = parser_out.info().node_num(d); + const float * feature_src = parser_in.feature_cpu(d); + float* feature_des = parser_out.mutable_feature_cpu(d); + for (int i = 0, j = 0; i < nnum_d; ++i) { + if (drop[d][i] == 0) { + for (int c = 0; c < channel_feature; ++c) { + feature_des[c * nnum_src + j] = feature_src[c * nnum_d + i]; + } + ++j; + } + } + } + + if ((location_label == -1 || d == depth) && channel_label != 0) { + const float * label_src = parser_in.label_cpu(d); + float* label_des = parser_out.mutable_label_cpu(d); + for (int i = 0, j = 0; i < nnum_d; ++i) { + if (drop[d][i] == 0) { + label_des[j] = label_src[i]; + ++j; + } + } + } + + if ((location_split == -1 || d == depth) && channel_split != 0) { + const float * split_src = parser_in.split_cpu(d); + float* split_des = parser_out.mutable_split_cpu(d); + for (int i = 0, j = 0; i < nnum_d; ++i) { + if (drop[d][i] == 0) { + split_des[j] = split_src[i]; + ++j; + } + } + } + } + + // modify the children and node_num_nempty + int id = 0; + const int* child_src = parser_in.children_cpu(depth_dropout); + int* child_des = parser_out.mutable_children_cpu(depth_dropout); + for (int i = 0; i < node_num[depth_dropout]; ++i) { + child_des[i] = (drop[depth_dropout][i] == 1 || child_src[i] < 0) ? + child_src[i] : id++; + } + for (int d = 0; d < depth_dropout; ++d) { + node_num_nempty[d] = parser_in.info().node_num_nempty(d); + } + node_num_nempty[depth_dropout] = id; // !!! important + parser_out.mutable_info().set_nempty(node_num_nempty.data()); +} diff --git a/octree/octree/transform_octree.h b/octree/octree/transform_octree.h new file mode 100644 index 0000000..fb0ebe5 --- /dev/null +++ b/octree/octree/transform_octree.h @@ -0,0 +1,35 @@ +#ifndef _OCTREE_TRANSFORM_OCTREES_ +#define _OCTREE_TRANSFORM_OCTREES_ + +#include "octree_parser.h" + +class ScanOctree { + public: + ScanOctree(float scale = 1.0f) : scale_(scale) {} + void set_scale(float scale); + void set_axis(const float* axis, int n = 3); + void scan(vector& octree_out, const OctreeParser& octree_in, + const vector& axis); + + protected: + void bbox_xy(int depth); + void reset_buffer(); + void z_buffer(vector& drop_flags, const OctreeParser& octree_in); + void generate_flags(vector>& drop_flags, const OctreeParser& octree_in); + void trim_octree(vector& octree_out, const OctreeParser& octree_in, + vector>& drop_flags); + + protected: + float scale_; // scale_ must be large than 0 + int width_; + vector id_buffer_; + vector z_buffer_; + + float bbmin_[3], bbmax_[3]; + float x_[3], y_[3], z_[3]; +}; + +void octree_dropout(vector& octree_output, const string& octree_input, + const int depth_dropout, const float threshold); + +#endif // _OCTREE_TRANSFORM_OCTREES_ diff --git a/octree/octree/transform_points.cpp b/octree/octree/transform_points.cpp new file mode 100644 index 0000000..339dcc7 --- /dev/null +++ b/octree/octree/transform_points.cpp @@ -0,0 +1,70 @@ +#include "transform_points.h" +#include +#include + +DropPoints::DropPoints(int dim, float ratio, const float* bbmin, const float* bbmax) { + dim_ = dim; + ratio_ = ratio; + for (int i = 0; i < 3; ++i) { + bbmin_[i] = bbmin[i]; + bbmax_[i] = bbmax[i]; + iwidth_[i] = (float) dim / (bbmax[i] - bbmin[i] + 1.0e-10f); + } +} + +int DropPoints::hash(const float* pt) { + float xyz[3] = { 0 }; + for (int i = 0; i < 3; ++i) { + xyz[i] = static_cast((pt[i] - bbmin_[i]) * iwidth_[i]); + } + int h = (xyz[0] * dim_ + xyz[1]) * dim_ + xyz[2]; + return h; +} + +void DropPoints::dropout(Points& points) { + if (ratio_ < 1.0e-5f) return; // trival case + std::default_random_engine generator(static_cast(time(nullptr))); + std::bernoulli_distribution distribution(ratio_); + + const int hash_num = dim_ * dim_ * dim_; + spatial_hash_.assign(hash_num, -1); + int pt_num = points.info().pt_num(), pt_num_remain = 0; + vector pt_flags(pt_num, -1); // -1 - uninitialized, 0 - keep, 1 - drop + const float* pts = points.points(); + + for (int i = 0, id = 0; i < pt_num; ++i) { + int h = hash(pts + 3 * i); + if (h >= hash_num) { h = h % hash_num; } + int hash_val = spatial_hash_[h]; + if (hash_val == -1) { + hash_val = distribution(generator); + spatial_hash_[h] = hash_val; + } + pt_flags[i] = hash_val; + if (hash_val == 0) { pt_num_remain++; } + } + + // keep at least one point + if (pt_num_remain == 0) { + pt_num_remain = 1; + pt_flags[0] = 0; + } + + // lazy delete: just move the content, do not re-allocate memory + for (int p = 0; p < PointsInfo::kPTypeNum; ++p) { + auto ptype = static_cast(1 << p); + float* ptr = points.mutable_ptr(ptype); + if (ptr == nullptr) continue; + int ch = points.info().channel(ptype); + for (int i = 0, j = 0; i < pt_num; ++i) { + if (pt_flags[i] == 1) continue; + for (int c = 0; c < ch; ++c) { + ptr[j * ch + c] = ptr[i * ch + c]; + } + j++; // update j + } + } + + // update node num + points.mutable_info().set_pt_num(pt_num_remain); +} \ No newline at end of file diff --git a/octree/octree/transform_points.h b/octree/octree/transform_points.h new file mode 100644 index 0000000..a297045 --- /dev/null +++ b/octree/octree/transform_points.h @@ -0,0 +1,22 @@ +#ifndef _OCTREE_TRANSFORM_POINTS_ +#define _OCTREE_TRANSFORM_POINTS_ + +#include "points.h" + +class DropPoints { + public: + DropPoints(int dim, float ratio, const float* bbmin, const float* bbmax); + void dropout(Points& points); + + protected: + int hash(const float* pt); + + protected: + int dim_; + float ratio_; + float bbmin_[3], bbmax_[3], iwidth_[3]; + vector spatial_hash_; +}; + + +#endif // _OCTREE_TRANSFORM_POINTS_ \ No newline at end of file diff --git a/octree/python/CMakeLists.txt b/octree/python/CMakeLists.txt new file mode 100644 index 0000000..4f949dd --- /dev/null +++ b/octree/python/CMakeLists.txt @@ -0,0 +1,20 @@ +# download the pybind11 +set(pybind11_path "${PROJECT_SOURCE_DIR}/external/octree-ext/pybind11") +# set(pybind11_file "${PROJECT_SOURCE_DIR}/external/pybind11.zip") +# if(NOT EXISTS ${pybind11_path}) +# file(DOWNLOAD +# https://github.com/pybind/pybind11/archive/master.zip +# ${pybind11_file} +# SHOW_PROGRESS +# ) +# execute_process(COMMAND ${CMAKE_COMMAND} -E tar xfz +# ${pybind11_file} +# WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/external) +# file(REMOVE ${pybind11_file}) +# endif() + +# add the python interface +add_subdirectory(${pybind11_path} ${pybind11_path}/build) +pybind11_add_module(pyoctree pyoctree.cpp) +target_link_libraries(pyoctree PRIVATE octree_lib) +set_target_properties(pyoctree PROPERTIES FOLDER "python") diff --git a/octree/python/pyoctree.cpp b/octree/python/pyoctree.cpp new file mode 100644 index 0000000..dfc4481 --- /dev/null +++ b/octree/python/pyoctree.cpp @@ -0,0 +1,137 @@ +#include + +#include +#include +#include + +#include +#include + +// qq add for octree +#include +#include +#include + + + +namespace py = pybind11; + +int add(int i, int j) { + return i + j; +} + +py::array_t make_array(const py::ssize_t size) { + // No pointer is passed, so NumPy will allocate the buffer + return py::array_t(size); +} + +PYBIND11_MODULE(pyoctree, m) { + // examples + m.def("add", &add); + m.def("subtract", [](int i, int j) { return i - j; }); + + //m.def("make_array", &make_array, + // py::return_value_policy::move); + // Return policy can be left default, i.e. return_value_policy::automatic + + // pyoctree interface + m.def("get_one_octree", [](const char *name) { + size_t size = 0; + const char* str = (const char*)octree::get_one_octree(name, &size); + return py::bytes(std::string(str, size)); + }); + + + // points interface + using vectorf = vector; + using vectorfc = const vector; + auto Points_set_points = (bool(Points::*)(vectorfc&, vectorfc&, vectorfc&, + vectorfc&))&Points::set_points; + auto Points_set_points_buffer = (void(Points::*)(vector&))&Points::set_points; + py::class_(m, "Points") + .def(py::init<>()) + .def("read_points", &Points::read_points) + .def("write_points", &Points::write_points) + .def("write_ply", &Points::write_ply) + .def("normalize", &Points::normalize) + .def("set_points", Points_set_points) + .def("set_points_buffer", Points_set_points_buffer) + .def("pts_num", [](const Points& pts) { + return pts.info().pt_num(); + }) + // todo: fix the functions points(), normals(), labels(), + // It is inefficient, since there is a memory-copy here + .def("points", [](const Points& pts) { + const float* ptr = pts.points(); + int num = pts.info().pt_num(); + return vectorf(ptr, ptr + num * 3); + }) + .def("normals", [](const Points& pts) { + const float* ptr = pts.normal(); + int num = pts.info().pt_num(); + return vectorf(ptr, ptr + num * 3); + }) + .def("features", [](const Points& pts) { + const float* ptr = pts.feature(); + int num = pts.info().pt_num(); + const int ch = pts.info().channel(PointsInfo::kFeature); + return vectorf(ptr, ptr + num * ch); + }) + .def("labels", [](const Points& pts) { + const float* ptr = pts.label(); + int num = pts.info().pt_num(); + return vectorf(ptr, ptr + num); + }) + .def("buffer", [](const Points& pts) { + const char* ptr = pts.data(); + return py::bytes(std::string(ptr, pts.info().sizeof_points())); + }); + + // qq: try octree interface + using vectoru = vector; + using vectori = vector; + py::class_(m, "Octree") + .def(py::init<>()) + .def("read_octree", &Octree::read_octree) + .def("write_octree", &Octree::write_octree) +// .def("info", &Octree::info) + .def("depth", [](const Octree& octree) { + return octree.info().depth(); + }) + .def("num_nodes", [](const Octree& octree, int depth) { +// int depth = octree.info().depth(); + int num = octree.info().node_num(depth); + return num; + }) + .def("num_nodes_total", [](const Octree& octree) { + int num = octree.info().total_nnum(); + return num; + }) + .def("keys", [](const Octree& octree, int depth) { +// int depth = octree.info().depth(); + const uint32_t* ptr = octree.key_cpu(depth); + int num = octree.info().node_num(depth); + return vectoru(ptr, ptr + num); + }) + .def("features", [](const Octree& octree, int depth) { +// int depth = octree.info().depth(); + const float* ptr = octree.feature_cpu(depth); + int num = octree.info().node_num(depth); + const int ch = octree.info().channel(OctreeInfo::kFeature); + return vectorf(ptr, ptr + num * ch); + }) + .def("info_size", [](const Octree& octree) { + int info_size = octree.info().sizeof_octinfo(); + return info_size; + }) + .def("children", [](const Octree& octree, int depth) { +// int depth = octree.info().depth(); + const int* ptr = octree.children_cpu(depth); + int num = octree.info().node_num(depth); + return vectori(ptr, ptr + num); + }) + .def("num_channel", [](const Octree& octree) { + const int ch = octree.info().channel(OctreeInfo::kFeature); + return ch; + }); +} diff --git a/octree/test/test_octree.cpp b/octree/test/test_octree.cpp new file mode 100644 index 0000000..14c33d3 --- /dev/null +++ b/octree/test/test_octree.cpp @@ -0,0 +1,331 @@ +#include +#include +#include +#include +#include "math_functions.h" + +class OctreeTest : public ::testing::Test { + protected: + void gen_test_point() { + vector pt{1.0f, 1.0f, 0.0f}; + vector normal{ 1.0f, 0.0f, 0.0f}; + vector feature{ 1.0f, -1.0f, 2.0f}; + vector label{ 0.0f}; + points.set_points(pt, normal, feature, label); + } + + void gen_test_pointcloud() { + vector pts { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0625f, 1.0625f, 0.0f}; + vector normals { 1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f }; + vector features{ 1.0f, -1.0f, 2.0f, -2.0f, 3.0f, -3.0f }; + vector labels { 0.0f, 2.0f, 2.0f }; + points.set_points(pts, normals, features, labels); + } + + void build_octree() { + octree_.build(oct_info_, points); + } + + void trim_octree() { + octree_.trim_octree(); + } + + void set_octree_info(const bool adaptive, const bool key2xyz, const bool split_label, + const float* bbmin, const float* bbmax) { + const bool node_dis = true, node_feature = false; + const int depth = 5, full_layer = 1, adaptive_layer = 3; + // The normal threshold is very large and has no effect to the adaptive octree + const float th_normal = 5.0f, th_distance = 3.0f; + + oct_info_.reset(); + oct_info_.set_batch_size(1); + oct_info_.set_depth(depth); + oct_info_.set_full_layer(full_layer); + oct_info_.set_adaptive_layer(adaptive_layer); + oct_info_.set_adaptive(adaptive); + oct_info_.set_node_dis(node_dis); + oct_info_.set_key2xyz(key2xyz); + oct_info_.set_threshold_normal(th_normal); + oct_info_.set_threshold_dist(th_distance); + oct_info_.set_bbox(bbmin, bbmax); + + // by default, the octree contains Key and Child + int channel = (key2xyz && depth > 8) ? 2 : 1; + oct_info_.set_channel(OctreeInfo::kKey, channel); + oct_info_.set_location(OctreeInfo::kKey, -1); + oct_info_.set_channel(OctreeInfo::kChild, 1); + oct_info_.set_location(OctreeInfo::kChild, -1); + + // set feature + const PointsInfo& pt_info = points.info(); + channel = pt_info.channel(PointsInfo::kNormal) + pt_info.channel(PointsInfo::kFeature); + if (node_dis) channel += 1; + oct_info_.set_channel(OctreeInfo::kFeature, channel); + // location = -1 means the features exist on every node + int location = (node_feature || adaptive) ? -1 : depth; + oct_info_.set_location(OctreeInfo::kFeature, location); + + // set label + if (pt_info.channel(PointsInfo::kLabel) == 1) { + oct_info_.set_channel(OctreeInfo::kLabel, 1); + location = (node_feature || adaptive) ? -1 : depth; + oct_info_.set_location(OctreeInfo::kLabel, location); + } + + // set split label + if (split_label) { + oct_info_.set_channel(OctreeInfo::kSplit, 1); + oct_info_.set_location(OctreeInfo::kSplit, -1); + } + + // Skip nnum_[], nnum_cum_[], nnum_nempty_[] and ptr_dis_[], + // these three properties can only be set when the octree is built. + } + + protected: + Points points; + Octree octree_; + OctreeInfo oct_info_; + +}; + +TEST_F(OctreeTest, TestOctreeBuild) { + const float bbmin[] = { 0.0f, 0.0f, 0.0f }; + const float bbmax[] = { 2.0f, 2.0f, 2.0f }; + const bool adaptive = false, key2xyz = false, calc_split_label = true; + this->gen_test_pointcloud(); + this->set_octree_info(adaptive, key2xyz, calc_split_label, bbmin, bbmax); + this->build_octree(); + + const OctreeInfo& info = octree_.info(); + const int depth = 5, full_layer = 1; + EXPECT_EQ(info.depth(), depth); + EXPECT_EQ(info.full_layer(), full_layer); + + // test node number + const int nnum[] = { 1, 8, 16, 16, 16, 16 }; + const int nnum_cum[] = { 0, 1, 9, 25, 41, 57, 73 }; + const int nnum_nempty[] = { 1, 2, 2, 2, 2, 3 }; + for (int d = 0; d <= depth; ++d) { + EXPECT_EQ(info.node_num(d), nnum[d]); + EXPECT_EQ(info.node_num_cum(d), nnum_cum[d]); + EXPECT_EQ(info.node_num_nempty(d), nnum_nempty[d]); + } + EXPECT_EQ(info.node_num_cum(depth + 1), nnum_cum[depth + 1]); + + // test the key + const unsigned int keys[] = { + 0, 0, 1, 2, 3, 4, 5, 6, 7, + 0, 1, 2, 3, 4, 5, 6, 7, 48, 49, 50, 51, 52, 53, 54, 55, + 0, 1, 2, 3, 4, 5, 6, 7, 384, 385, 386, 387, 388, 389, 390, 391, + 0, 1, 2, 3, 4, 5, 6, 7, 3072, 3073, 3074, 3075, 3076, 3077, 3078, 3079, + 0, 1, 2, 3, 4, 5, 6, 7, 24576, 24577, 24578, 24579, 24580, 24581, 24582, 24583 + }; + for (int d = 0, j = 0; d <= depth; ++d) { + const int nnum_d = info.node_num(d); + const unsigned int* key_d = octree_.key_cpu(d); + for (int i = 0; i < nnum_d; ++i, j++) { + EXPECT_EQ(key_d[i], keys[j]); + } + } + + // test the children + const int children[] = { + 0, 0, -1, -1, -1, -1, -1, 1, -1, + 0, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, + 0, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, + 0, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, + 0, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 2, -1, + }; + for (int d = 0, j = 0; d <= depth; ++d) { + const int nnum_d = info.node_num(d); + const int* child_d = octree_.children_cpu(d); + for (int i = 0; i < nnum_d; ++i, j++) { + EXPECT_EQ(child_d[i], children[j]); + } + } + + // test the signal + const float features[] = { + 1.0f, 0, 0, 0, 0, 0, 0, 0, -1.0f, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.0f, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // normals + -0.57735f, 0, 0, 0, 0, 0, 0, 0, 0.57735f, 0, 0, 0, 0, 0, -0.57735f, 0, // displacement + 1.0f, 0, 0, 0, 0, 0, 0, 0, 2.0f, 0, 0, 0, 0, 0, 3.0f, 0, + -1.0f, 0, 0, 0, 0, 0, 0, 0, -2.0f, 0, 0, 0, 0, 0, -3.0f, 0 // other features + }; + const int channel = 6; + const int nnum_d = info.node_num(depth); + const float* feature_d = octree_.feature_cpu(depth); + EXPECT_EQ(channel, info.channel(OctreeInfo::kFeature)); + for (int i = 0; i < nnum_d * channel; ++i) { + EXPECT_EQ(feature_d[i], features[i]); + } + + // test the label + const float labels[] = { + 0.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, + 2.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 2.0f, -1.0f + }; + const float* label_d = octree_.label_cpu(depth); + for (int i = 0; i < nnum_d; ++i) { + EXPECT_EQ(label_d[i], labels[i]); + } + + // test the split label + const float split_labels[] = { + 1.0f, 1.0f, 0, 0, 0, 0, 0, 1.0f, 0, + 1.0f, 0, 0, 0, 0, 0, 0, 0, 1.0f, 0, 0, 0, 0, 0, 0, 0, + 1.0f, 0, 0, 0, 0, 0, 0, 0, 1.0f, 0, 0, 0, 0, 0, 0, 0, + 1.0f, 0, 0, 0, 0, 0, 0, 0, 1.0f, 0, 0, 0, 0, 0, 0, 0, + 1.0f, 0, 0, 0, 0, 0, 0, 0, 1.0f, 0, 0, 0, 0, 0, 1.0f, 0, + }; + for (int d = 0, j = 0; d <= depth; ++d) { + const int nnum_d = info.node_num(d); + const float* split_d = octree_.split_cpu(d); + for (int i = 0; i < nnum_d; ++i, j++) { + EXPECT_EQ(split_d[i], split_labels[j]); + } + } + + // test the fval + OctreeValue octree_value(&octree_); + EXPECT_EQ(octree_value.fval(16.5f, 16.5f, 0.5f).first, -0.5f); +} + +TEST_F(OctreeTest, TestOctreeTrim) { + const float bbmin[] = { 0.0f, 0.0f, 0.0f }; + const float bbmax[] = { 2.0f, 2.0f, 2.0f }; + const bool adaptive = true, key2xyz = false, calc_split_label = true; + this->gen_test_pointcloud(); + this->set_octree_info(adaptive, key2xyz, calc_split_label, bbmin, bbmax); + this->build_octree(); + this->trim_octree(); + + const OctreeInfo& info = octree_.info(); + const int depth = 5, full_layer = 1, depth_adpt = 3; + EXPECT_EQ(info.depth(), depth); + EXPECT_EQ(info.full_layer(), full_layer); + EXPECT_EQ(info.adaptive_layer(), depth_adpt); + + // test node number + const int nnum[] = { 1, 8, 16, 16, 16, 8 }; + const int nnum_cum[] = { 0, 1, 9, 25, 41, 57, 65 }; + const int nnum_nempty[] = { 1, 2, 2, 2, 1, 1 }; + for (int d = 0; d <= depth; ++d) { + EXPECT_EQ(info.node_num(d), nnum[d]); + EXPECT_EQ(info.node_num_cum(d), nnum_cum[d]); + EXPECT_EQ(info.node_num_nempty(d), nnum_nempty[d]); + } + EXPECT_EQ(info.node_num_cum(depth + 1), nnum_cum[depth + 1]); + + // test the key + const unsigned int keys[] = { + 0, 0, 1, 2, 3, 4, 5, 6, 7, + 0, 1, 2, 3, 4, 5, 6, 7, 48, 49, 50, 51, 52, 53, 54, 55, + 0, 1, 2, 3, 4, 5, 6, 7, 384, 385, 386, 387, 388, 389, 390, 391, + 0, 1, 2, 3, 4, 5, 6, 7, 3072, 3073, 3074, 3075, 3076, 3077, 3078, 3079, + 0, 1, 2, 3, 4, 5, 6, 7 + }; + for (int d = 0, j = 0; d <= depth; ++d) { + const int nnum_d = info.node_num(d); + const unsigned int* key_d = octree_.key_cpu(d); + for (int i = 0; i < nnum_d; ++i, j++) { + EXPECT_EQ(key_d[i], keys[j]); + } + } + + // test the children + const int children[] = { + 0, 0, -1, -1, -1, -1, -1, 1, -1, + 0, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, + 0, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, + 0, -1, -1, -1, -1, -1, -1, -1, -1/**/, -1, -1, -1, -1, -1, -1, -1, + 0, -1, -1, -1, -1, -1, -1, -1 + }; + for (int d = 0, j = 0; d <= depth; ++d) { + const int nnum_d = info.node_num(d); + const int* child_d = octree_.children_cpu(d); + for (int i = 0; i < nnum_d; ++i, j++) { + EXPECT_EQ(child_d[i], children[j]); + } + } + + //// test the signal + const float feature0[] = { + 0, 1.0f, 0, -0.180422f, 2.0f, -2.0f + }; + const float feature1[] = { + 1.0f, 0, 0, 0, 0, 0, -0.707107f, 0, + 0, 0, 0, 0, 0, 0, 0.707107f, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + -0.57735f, 0, 0, 0, 0, 0, 0, 0, + 1.0f, 0, 0, 0, 0, 0, 2.5f, 0, + -1.0f, 0, 0, 0, 0, 0, -2.5f, 0 + }; + const float feature2[] = { + 1.0f, 0, 0, 0, 0, 0, 0, 0, -0.707107f, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0.707107f, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + -0.57735f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1.0f, 0, 0, 0, 0, 0, 0, 0, 2.5f, 0, 0, 0, 0, 0, 0, 0, + -1.0f, 0, 0, 0, 0, 0, 0, 0, -2.5f, 0, 0, 0, 0, 0, 0, 0 + }; + const float* feature3 = feature2; + const float* feature4 = feature2; + const float feature5[] = { + 1.0f, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + -0.57735f, 0, 0, 0, 0, 0, 0, 0, + 1.0f, 0, 0, 0, 0, 0, 0, 0, + -1.0f, 0, 0, 0, 0, 0, 0, 0 + }; + const float* features[] = { feature0, feature1, feature2, feature3, feature4, feature5}; + const int channel = 6; + const int nnum_d = info.node_num(depth); + EXPECT_EQ(channel, info.channel(OctreeInfo::kFeature)); + + for (int d = 0; d <= depth; ++d) { + const int nnum_d = info.node_num(d); + const float* feature_d = octree_.feature_cpu(d); + for (int i = 0; i < nnum_d * channel; ++i) { + EXPECT_FLOAT_EQ(features[d][i], feature_d[i]) << i; + } + } + + // test the label + const float labels[] = { + 2.0f, 0, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 2.0f, -1.0f, + 0, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, + 2.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, + 0, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, + 2.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, + 0, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, + 2.0f/**/, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, + 0, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f + }; + for (int d = 0, j = 0; d <= depth; ++d) { + const int nnum_d = info.node_num(d); + const float* label_d = octree_.label_cpu(d); + for (int i = 0; i < nnum_d; ++i, j++) { + EXPECT_EQ(label_d[i], labels[j]); + } + } + + // test the split label + const float split_labels[] = { + 1.0f, 1.0f, 0, 0, 0, 0, 0, 1.0f, 0, + 1.0f, 0, 0, 0, 0, 0, 0, 0, 1.0f, 0, 0, 0, 0, 0, 0, 0, + 1.0f, 0, 0, 0, 0, 0, 0, 0, 1.0f, 0, 0, 0, 0, 0, 0, 0, + 1.0f, 0, 0, 0, 0, 0, 0, 0, 2.0f, 0, 0, 0, 0, 0, 0, 0, + 1.0f, 0, 0, 0, 0, 0, 0, 0 + }; + for (int d = 0, j = 0; d <= depth; ++d) { + const int nnum_d = info.node_num(d); + const float* split_d = octree_.split_cpu(d); + for (int i = 0; i < nnum_d; ++i, j++) { + EXPECT_EQ(split_d[i], split_labels[j]); + } + } +} \ No newline at end of file diff --git a/octree/test/test_octree_nn.cpp b/octree/test/test_octree_nn.cpp new file mode 100644 index 0000000..02760c4 --- /dev/null +++ b/octree/test/test_octree_nn.cpp @@ -0,0 +1,94 @@ +#include +#include +#include +#include + + +TEST(VecResizeTest, TestVecResize) { + vector vec0{ 3 }, gt0{ 3, 3, 3 }; + resize_with_last_val(vec0, 3); + ASSERT_EQ(vec0, gt0); + + vector vec1{ 3, 1, 1, 3 }, gt1{3, 1, 1}; + resize_with_last_val(vec1, 3); + ASSERT_EQ(vec1, gt1); + + vector vec2, gt2; + resize_with_last_val(vec2, 3); + ASSERT_EQ(vec2, gt2); +} + + +TEST(BiliearNeigh, TestBiliearNeigh) { + const char* octree1 = (const char*) octree::get_one_octree("octree_1"); + vector buffer; + merge_octrees(buffer, vector {octree1}); + + OctreeParser parser; + parser.set_cpu(buffer.data()); + + int depth = 4; + vector bidx(parser.info().node_num(depth + 1) * 8); + const int* child0 = parser.children_cpu(depth); + const int nnum0 = parser.info().node_num(depth); + const int nnum1 = parser.info().node_num(depth + 1); + bilinear_neigh_cpu(bidx.data(), parser.neighbor_cpu(depth), + child0, nnum0, NeighHelper::get_bilinear_array().data()); + + // check + const int weights[8] = { 27, 9, 9, 9, 3, 3, 3, 1 }; + const unsigned* key0 = parser.key_cpu(depth); + const unsigned* key1 = parser.key_cpu(depth + 1); + ASSERT_TRUE(parser.info().is_key2xyz()); + auto key_to_xyz = [](float * xyz, const unsigned * key) { + const unsigned char* ptr = (const unsigned char*)key; + for (int i = 0; i < 3; ++i) { xyz[i] = (float)ptr[i] + 0.5f; } + }; + for (int i = 0; i < nnum1; ++i) { + float xyz0[3], xyz1[3]; + key_to_xyz(xyz1, key1 + i); + for (int c = 0; c < 3; ++c) { + xyz1[c] /= 2.0f; + } + + for (int k = 0; k < 8; ++k) { + int j = bidx[i * 8 + k]; + if (j < 0) continue; + key_to_xyz(xyz0, key0 + j); + + int weight = 1; + for (int c = 0; c < 3; ++c) { + float dis = fabs(xyz0[c] - xyz1[c]); + ASSERT_LT(dis, 1.0f); + weight *= (int)((1 - dis) * 4); + } + ASSERT_EQ(weight, weights[k]); + } + } + + // check + vector xyz10(nnum1 * 8), key10(nnum1 * 8), key_octree(nnum0); + vector fracs(nnum1 * 3); + bilinear_xyz_cpu(xyz10.data(), fracs.data(), depth, + parser.key_cpu(depth + 1), depth + 1, nnum1); + xyz2key_cpu(key10.data(), xyz10.data(), xyz10.size(), depth); + xyz2key_cpu(key_octree.data(), key0, nnum0, depth); + vector sidx(nnum1 * 8); + search_key_cpu(sidx.data(), key_octree.data(), key_octree.size(), + key10.data(), key10.size()); + for (int i = 0; i < sidx.size(); ++i) { + ASSERT_EQ(sidx[i], bidx[i]); + } +} + + +TEST(Coord2xyzTest, TestCoord2xyz) { + float coord[] = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8 }; + unsigned char xyz[] = { 1, 3, 5, 7, 2, 4, 6, 8 }; + unsigned int rst[2] = { 0 }; + coord2xyz_cpu(rst, coord, 2, 4); + unsigned char* ptr = (unsigned char*)rst; + for (int i = 0; i < 8; ++i) { + ASSERT_EQ(ptr[i], xyz[i]); + } +} \ No newline at end of file diff --git a/octree/test/test_util.cpp b/octree/test/test_util.cpp new file mode 100644 index 0000000..8cde2e9 --- /dev/null +++ b/octree/test/test_util.cpp @@ -0,0 +1,97 @@ +#include +#include "math_functions.h" +#include +#include + +TEST(UtilTest, TestExtractPath) { + EXPECT_EQ(extract_path("C:\\test\\test.txt"), "C:/test"); + EXPECT_EQ(extract_path("C:/test\\test.txt"), "C:/test"); + EXPECT_EQ(extract_path("C:/test"), "C:"); + EXPECT_EQ(extract_path("test.txt"), "."); + EXPECT_EQ(extract_path("./test.txt"), "."); +} + +TEST(UtilTest, TestExtractFilename) { + EXPECT_EQ(extract_filename("C:\\test\\test.txt"), "test"); + EXPECT_EQ(extract_filename("C:/test\\test.txt"), "test"); + EXPECT_EQ(extract_filename("C:/test"), "test"); + EXPECT_EQ(extract_filename("test.txt"), "test"); + EXPECT_EQ(extract_filename("./test.txt"), "test"); + EXPECT_EQ(extract_filename("test"), "test"); +} + +TEST(UtilTest, TestExtractSuffix) { + EXPECT_EQ(extract_suffix("C:\\test\\test.txt"), "txt"); + EXPECT_EQ(extract_suffix("C:/test\\test.TXT"), "txt"); + EXPECT_EQ(extract_suffix("C:/test"), ""); +} + +TEST(MeshTest, TestFaceCenter) { + vector V{ 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0}; + vector F{0, 1, 3, 1, 2, 3}; + vector center{ 1.0f / 3.0f, 1.0f / 3.0f, 0, 2.0f / 3.0f, 2.0f / 3.0f, 1.0f / 3.0f }; + vector center_test; + compute_face_center(center_test, V, F); + ASSERT_EQ(center_test.size(), center.size()); + for (int i = 0; i < center.size(); ++i) { + EXPECT_FLOAT_EQ(center[i], center_test[i]); + } +} + +TEST(MeshTest, TestFaceNormal) { + vector V{ 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0 }; + vector F{ 0, 1, 3, 1, 2, 3 }; + float t = sqrtf(1.0f / 3.0f), q = sqrtf(3.0f) * 0.5f; + vector face_normal{0, 0, 1.0f, -t, -t, t }; + vector face_area{ 0.5f, q }; + vector normal_test, area_test; + compute_face_normal(normal_test, area_test, V, F); + ASSERT_EQ(normal_test.size(), face_normal.size()); + for (int i = 0; i < normal_test.size(); ++i) { + EXPECT_FLOAT_EQ(normal_test[i], face_normal[i]); + } + + ASSERT_EQ(area_test.size(), face_area.size()); + for (int i = 0; i < area_test.size(); ++i) { + EXPECT_FLOAT_EQ(area_test[i], face_area[i]); + } +} + +TEST(MathTest, TestRotMatrix1) { + float angles[3] = { 0.1f, 0.2f, 0.3f }, rot[9]; + rotation_matrix(rot, angles); + + float rotx[9], roty[9], rotz[9], rot_gt[9], tmp[9]; + float x[3] = { 1.0f, 0, 0 }, y[3] = { 0, 1.0f, 0 }, z[3] = { 0, 0, 1.0f }; + rotation_matrix(rotx, angles[0], x); + rotation_matrix(roty, angles[1], y); + rotation_matrix(rotz, angles[2], z); + matrix_prod(tmp, rotx, roty, 3, 3, 3); + matrix_prod(rot_gt, tmp, rotz, 3, 3, 3); + + for (int i = 0; i < 9; ++i) { + EXPECT_EQ(rot[i], rot_gt[i]); + } +} + +TEST(MathTest, TestRotMatrix2) { + const float kPI = 3.1415925f; + float angle1[3] = { -10.0, -20.0f, -210.0f }, rot1[9]; + float angle2[3] = { 350.0f, 340.0f, 150.0f }, rot2[9]; + for (int i = 0; i < 3; ++i) { + angle1[i] = angle1[i] * kPI / 180.0f; + angle2[i] = angle2[i] * kPI / 180.0f; + } + rotation_matrix(rot1, angle1); + rotation_matrix(rot2, angle2); + + for (int i = 0; i < 9; ++i) { + ASSERT_NEAR(rot1[i], rot2[i], 1.0e-5); + } +} + + +//int main(int argc, char **argv) { +// ::testing::InitGoogleTest(&argc, argv); +// return RUN_ALL_TESTS(); +//} \ No newline at end of file diff --git a/octree/tools/.DS_Store b/octree/tools/.DS_Store new file mode 100644 index 0000000..6347f9d Binary files /dev/null and b/octree/tools/.DS_Store differ diff --git a/octree/tools/adaptive_octree.cpp b/octree/tools/adaptive_octree.cpp new file mode 100644 index 0000000..68c6250 --- /dev/null +++ b/octree/tools/adaptive_octree.cpp @@ -0,0 +1,507 @@ +// todo: optimize the coding + +#include +#include +#include +#include +#include +#include + +#include "filenames.h" +#include "octree.h" +#include "marching_cube.h" + +using namespace std; + +int depth_start = 4; +int signal_channel = 4; +int segmentation = 0; +int split_label = 0; + +//float threshold = 0.002f; // about 2.6 degree +float threshold = 0.1f; // about 5.7 degree +float threshold_distance = 0.866; // sqrtf(3.0f) * 0.5f; +//float threshold = 0.02f; // about 8.1 degree +//float threshold = 0.05f; // about 12.8 degree +//float threshold = 0.2f; // about 12.8 degree + + +void covered_depth_nodes(vector>& dnum_, vector>& didx_, + const int* children_, const int* node_num_, const int* node_num_accu_, const int depth_) { + // init + didx_.resize(depth_ + 1); + dnum_.resize(depth_ + 1); + + //layer-depth_ + int nnum = node_num_[depth_]; + dnum_[depth_].resize(nnum, 1); + didx_[depth_].resize(nnum); + for (int i = 0; i < nnum; ++i) { + didx_[depth_][i] = i; + } + + // layer-(depth_-1) + nnum = node_num_[depth_ - 1]; + dnum_[depth_ - 1].resize(nnum, 0); + didx_[depth_ - 1].resize(nnum, -1); + const int* children_d = children_ + node_num_accu_[depth_ - 1]; + for (int i = 0; i < nnum; ++i) { + int t = children_d[i]; + if (t != -1) { + dnum_[depth_ - 1][i] = 8; + didx_[depth_ - 1][i] = t * 8; + } + } + + // layer-(depth-2) to layer-0 + for (int d = depth_ - 2; d >= 0; --d) { + nnum = node_num_[d]; + dnum_[d].resize(nnum, 0); + didx_[d].resize(nnum, -1); + const int* children_d = children_ + node_num_accu_[d]; + for (int i = 0; i < nnum; ++i) { + int t = children_d[i]; + if (t != -1) { + t *= 8; + for (int j = 0; j < 8; ++j) { + dnum_[d][i] += dnum_[d + 1][t + j]; + } + for (int j = 0; j < 8; ++j) { + if (didx_[d + 1][t + j] != -1) { + didx_[d][i] = didx_[d + 1][t + j]; + break; + } + } + } + } + } +} + + +void adaptive_octree(vector& octree_output, const vector& octree_input, + const int depth_output) { + /// const + const float mul = sqrtf(3.0f) / 2.0f; + const float imul = 2.0f / sqrtf(3.0f); + //const int signal_channel = 4; + + /// parse the octree file + Octree octree_in; + octree_in.set_octree(octree_input.data(), octree_input.size()); + bool is_key2xyz = octree_in.info().is_key2xyz(); // !!! must be true + int depth = octree_in.info().depth(); + int full_layer = octree_in.info().full_layer(); + int total_node_num = octree_in.info().total_nnum(); + int final_node_num = octree_in.info().node_num(depth); + vector nnum_vec(depth + 1, 0), nnum_accu_vec(depth + 2, 0); + for (int d = 0; d < depth + 1; ++d) { + nnum_vec[d] = octree_in.info().node_num(d); + nnum_accu_vec[d] = octree_in.info().node_num_cum(d); + } + nnum_accu_vec[depth + 1] = octree_in.info().node_num_cum(depth + 1); + const int* node_num = nnum_vec.data(); + const int* node_num_accu = nnum_accu_vec.data(); + const unsigned int* key = octree_in.key_cpu(0); + const int* children = octree_in.children_cpu(0); + const float* data = octree_in.feature_cpu(0); + const float* normal_ptr = data; // !!! channel x n + const float* dis_ptr = normal_ptr + 3 * final_node_num; + const float* label_ptr = octree_in.label_cpu(0); + + /// precompute the nodes in the depth layer covered by each octree node + vector> dnum_, didx_; + covered_depth_nodes(dnum_, didx_, children, node_num, node_num_accu, depth); + + /// precompute the points in the finest level + vector pt_depth; // !!! n x channel + if (signal_channel == 4) { + pt_depth.resize(3 * final_node_num); + const unsigned int* key_depth = key + node_num_accu[depth]; + for (int i = 0; i < final_node_num; ++i) { + const unsigned char* pt = reinterpret_cast(key_depth + i); + for (int c = 0; c < 3; ++c) { + float nc = normal_ptr[c * final_node_num + i]; + pt_depth[i * 3 + c] = static_cast(pt[c]) + 0.5f + dis_ptr[i] * nc * mul; + } + } + } + + /// the average normal & displacement and normal variation + vector > normal_avg(depth + 1), normal_err(depth + 1), distance_err(depth + 1); + vector > label_avg(depth + 1); + int nlabel = 0; + if (segmentation != 0) { + nlabel = *std::max_element(label_ptr, label_ptr + final_node_num) + 1; + } + + // initialization + for (int d = 0; d <= depth; ++d) { + normal_avg[d].resize(signal_channel * node_num[d], 0.0f); + normal_err[d].resize(node_num[d], 5.0f); // !!! initialized as 5.0f + distance_err[d].resize(node_num[d], 5.0e10f); + if (segmentation != 0) { + label_avg[d].resize(node_num[d], -1); + } + } + + // for the depth layer + memcpy(normal_avg[depth].data(), normal_ptr, normal_avg[depth].size() * sizeof(float)); + if (segmentation != 0) { + memcpy(label_avg[depth].data(), label_ptr, label_avg[depth].size() * sizeof(float)); + } + + // for the other layers + for (int d = depth - 1; d > full_layer; --d) { + vector& dnum = dnum_[d]; + vector& didx = didx_[d]; + const unsigned int* key_d = key + node_num_accu[d]; + const int* children_d = children + node_num_accu[d]; + const int* children_depth = children + node_num_accu[depth]; + const float scale = static_cast(1 << (depth - d)); + + int nnum = node_num[d]; + vector& normal_d = normal_avg[d]; + vector& normal_err_d = normal_err[d]; + vector& distance_err_d = distance_err[d]; + + vector& label_d = label_avg[d]; + for (int i = 0; i < nnum; ++i) { + if (children_d[i] == -1) continue; + + // average the normal and projection point + float count = 0.0f; + float pt_avg[3] = { 0.0f, 0.0f, 0.0f }, dis_avg = 0.0f; + float n_avg[3] = { 0.0f, 0.0f, 0.0f }; + vector l_avg(nlabel, 0); + for (int j = didx[i]; j < didx[i] + dnum[i]; ++j) { + if (children_depth[j] == -1) continue; + count += 1.0f; + for (int c = 0; c < 3; ++c) { + float nc = normal_ptr[c * final_node_num + j]; + n_avg[c] += nc; + if (signal_channel == 4) { + pt_avg[c] += pt_depth[3 * j + c]; + } + } + if (segmentation != 0) { + l_avg[label_ptr[j]] += 1; + } + } + + + float len = 1.0e-30f; + for (int c = 0; c < 3; ++c) len += n_avg[c] * n_avg[c]; + len = sqrtf(len); + + float pt_base[3]; + const unsigned char* pt = reinterpret_cast(key_d + i); + for (int c = 0; c < 3; ++c) { + n_avg[c] /= len; + if (signal_channel == 4) { + pt_avg[c] /= count * scale; // !!! note the scale + pt_base[c] = static_cast(pt[c]); + float fract_part = pt_avg[c] - pt_base[c]; + dis_avg += (fract_part - 0.5f) * n_avg[c]; + } + } + + // === version 1 + // the normal error + float nm_err = 0.0f; + for (int j = didx[i]; j < didx[i] + dnum[i]; ++j) { + if (children_depth[j] == -1) continue; + for (int c = 0; c < 3; ++c) { + float tmp = normal_ptr[c * final_node_num + j] - n_avg[c]; + nm_err += tmp * tmp; + } + } + nm_err /= count; + + // output + normal_d[i] = n_avg[0]; + normal_d[1 * nnum + i] = n_avg[1]; + normal_d[2 * nnum + i] = n_avg[2]; + if (signal_channel == 4) { + normal_d[3 * nnum + i] = dis_avg * imul; // IMPORTANT: RESCALE + } + normal_err_d[i] = nm_err; + if (segmentation != 0) { + label_d[i] = 0; + for (int j = 1, v = 0; j < nlabel; ++j) { + if (l_avg[j] > v) { + v = l_avg[j]; + label_d[i] = j; + } + } + } + + // === version 2 + if (signal_channel != 4) { + distance_err_d[i] = 0; + continue; + } + // the error from the original geometry to the averaged geometry + float distance_max1 = -1; + // !!! note the scale + float pt_avg1[3] = { pt_avg[0] * scale, pt_avg[1] * scale, pt_avg[2] * scale }; + for (int j = didx[i]; j < didx[i] + dnum[i]; ++j) { + if (children_depth[j] == -1) continue; + float dis = 0.0f; + for (int c = 0; c < 3; ++c) { + dis += (pt_depth[3 * j + c] - pt_avg1[c]) * n_avg[c]; + } + dis = abs(dis); + if (dis > distance_max1) distance_max1 = dis; + } + + // the error from the averaged geometry to the original geometry + float distance_max2 = -1; + vector vtx; + intersect_cube(vtx, pt_avg, pt_base, n_avg); + if (vtx.empty()) distance_max2 = 5.0e10f; // !!! the degenerated case, ||n_avg|| == 0 + for (auto& v : vtx) v *= scale; // !!! note the scale + for (int k = 0; k < vtx.size() / 3; ++k) { + + // min + float distance_min = 1.0e30f; + for (int j = didx[i]; j < didx[i] + dnum[i]; ++j) { + if (children_depth[j] == -1) continue; + float dis = 0.0f; + for (int c = 0; c < 3; ++c) { + float ptc = pt_depth[3 * j + c] - vtx[3 * k + c]; + dis += ptc * ptc; + } + dis = sqrtf(dis); + if (dis < distance_min) distance_min = dis; + } + + // max + if (distance_min > distance_max2) distance_max2 = distance_min; + } + + distance_err_d[i] = std::max(distance_max2, distance_max1); + } + } + + /// trim the octree according to normal_var + vector > data_output(depth + 1), label_output(depth + 1); + vector > key_output(depth + 1); + vector > children_output(depth + 1), drop(depth + 1); + for (int d = 0; d <= depth; ++d) { + drop[d].resize(node_num[d], 0); // 1 means dropping the sub-tree + } + for (int d = depth_output; d <= depth; ++d) { + int nnum_d = node_num[d]; + int nnum_dp = node_num[d - 1]; + vector& normal_err_d = normal_err[d]; + vector& dist_err_d = distance_err[d]; + const unsigned int* key_d = key + node_num_accu[d]; + const int* children_d = children + node_num_accu[d]; + const int* children_dp = children + node_num_accu[d - 1]; + vector& drop_d = drop[d]; + vector& drop_dp = drop[d - 1]; + + // generate the drop flag + bool all_drop = true; + for (int i = 0; i < nnum_dp; ++i) { + int t = children_dp[i]; + if (t == -1) continue; + for (int j = 0; j < 8; ++j) { + int idx = t * 8 + j; + drop_d[idx] = drop_dp[i] == 1 || + (normal_err_d[idx] < threshold && dist_err_d[idx] < threshold_distance); + //drop_d[idx] = drop_dp[i] == 1 ||dist_err_d[idx] < thredhold_distance; + if (all_drop && children_d[idx] != -1) { + all_drop = drop_d[idx] == 1; + } + } + } + + // make sure that there is at least one octree node in each layer + if (all_drop) { + int max_idx = 0; + float max_var = -1.0f; + for (int i = 0; i < nnum_dp; ++i) { + int t = children_dp[i]; + if (t == -1 || drop_dp[i] == 1) continue; + for (int j = 0; j < 8; ++j) { + int idx = t * 8 + j; + if (children_d[idx] != -1 && normal_err_d[idx] > max_var) { + max_var = normal_err_d[idx]; + max_idx = idx; + } + } + } + drop_d[max_idx] = 0; + } + + for (int i = 0, id = 0; i < nnum_dp; ++i) { + int t = children_dp[i]; + if (t == -1) continue; + for (int j = 0; j < 8; ++j) { + int idx = t * 8 + j; + if (drop_dp[i] == 0) { + key_output[d].push_back(key_d[idx]); + + int ch = (drop_d[idx] == 0 && children_d[idx] != -1) ? id++ : -1; + children_output[d].push_back(ch); + + for (int c = 0; c < signal_channel; ++c) { + data_output[d].push_back(normal_avg[d][c * nnum_d + idx]); + } + + if (segmentation != 0) { + label_output[d].push_back(label_avg[d][idx]); + } + } + } + } + + // transpose data + int num = key_output[d].size(); + vector data_buffer(num * signal_channel); + for (int i = 0; i < num; ++i) { + for (int c = 0; c < signal_channel; ++c) { + data_buffer[c * num + i] = data_output[d][i * signal_channel + c]; + } + } + data_output[d].swap(data_buffer); + } + + /// output + OctreeInfo info_out = octree_in.info(); + info_out.set_adaptive(true); + info_out.set_threshold_dist(threshold_distance); + info_out.set_threshold_normal(threshold); + if (split_label) { + info_out.set_property(OctreeInfo::kSplit, 1, -1); + } else { + info_out.set_property(OctreeInfo::kSplit, 0, 0); + } + info_out.set_property(OctreeInfo::kFeature, signal_channel, -1); + if (segmentation) info_out.set_property(OctreeInfo::kLabel, 1, -1); + //vector nnum_nempty_vec(depth + 1); + for (int d = 0; d <= depth; ++d) { + nnum_vec[d] = d <= depth_output ? octree_in.info().node_num(d) : key_output[d].size(); + } + info_out.set_nnum(nnum_vec.data()); + info_out.set_nnum_cum(); + info_out.set_ptr_dis(); + + // copy OctreeInfo + Octree octree_out; + octree_out.resize_octree(info_out.sizeof_octree()); + octree_out.mutable_info() = info_out; + copy(key, key + node_num_accu[depth_output], octree_out.mutable_key_cpu(0)); + for (int d = depth_output; d <= depth; ++d) { + copy(key_output[d].begin(), key_output[d].end(), octree_out.mutable_key_cpu(d)); + } + copy(children, children + node_num_accu[depth_output], octree_out.mutable_children_cpu(0)); + for (int d = depth_output; d <= depth; ++d) { + copy(children_output[d].begin(), children_output[d].end(), octree_out.mutable_children_cpu(d)); + } + for (int d = 0; d <= depth; ++d) { + vector& normal_tmp = d < depth_output ? normal_avg[d] : data_output[d]; + copy(normal_tmp.begin(), normal_tmp.end(), octree_out.mutable_feature_cpu(d)); + } + if (segmentation != 0) { + for (int d = 0; d <= depth; ++d) { + vector& label_tmp = d < depth_output ? label_avg[d] : label_output[d]; + copy(label_tmp.begin(), label_tmp.end(), octree_out.mutable_label_cpu(d)); + } + } + // update nnum_nempty + for (int d = 0; d <= depth; ++d) { + // find the last element which is not equal to -1 + int nnum_nempty = 0; + const int* children_d = octree_out.children_cpu(d); + for (int i = octree_out.info().node_num(d) - 1; i >= 0; i--) { + if (children_d[i] != -1) { + nnum_nempty = children_d[i] + 1; + break; + } + } + nnum_vec[d] = nnum_nempty; + } + octree_out.mutable_info().set_nempty(nnum_vec.data()); + + if (split_label != 0) { + // generate split label according to the children_ + vector > split_output(depth + 1); + for (int d = 0; d <= depth; ++d) { + int nnum_d = octree_out.info().node_num(d); + vector& split_d = split_output[d]; + split_d.resize(nnum_d, 1); // initialize as 1 + const int* children_d = d < depth_output ? + children + node_num_accu[d] : children_output[d].data(); + vector& data_d = data_output[d]; + for (int i = 0; i < nnum_d; ++i) { + if (children_d[i] == -1) { + split_d[i] = 0; + if (d >= depth_output) { + float t = abs(data_d[i]) + abs(data_d[nnum_d + i]) + abs(data_d[nnum_d * 2 + i]); + if (t != 0) split_d[i] = 2; + } + } + } + } + for (int d = 0; d <= depth; ++d) { + copy(split_output[d].begin(), split_output[d].end(), octree_out.mutable_split_cpu(d)); + } + } + + octree_output = octree_out.buffer(); +} + + +void adaptive_octree(const string& filename, const string& filename_output) { + // read octree + ifstream infile(filename, ios::binary); + infile.seekg(0, infile.end); + int len = infile.tellg(); + infile.seekg(0, infile.beg); + vector octree(len, 0); + infile.read(octree.data(), len); + infile.close(); + + vector octree_output; + adaptive_octree(octree_output, octree, depth_start); + + // save octree + ofstream outfile(filename_output, ios::binary); + outfile.write(octree_output.data(), octree_output.size()); + outfile.close(); +} + + +int main(int argc, char* argv[]) { + if (argc < 3) { + cout << "Usage: AdaptiveOctree.exe " + << "[depth] [signal_channel] [segmentation] [split_label]" << endl; + return 0; + } + + string input_file_path(argv[1]); + string output_file_path(argv[2]); + mkdir(output_file_path.c_str()); + if (argc > 3) depth_start = atoi(argv[3]); + if (argc > 4) signal_channel = atoi(argv[4]); + if (argc > 5) segmentation = atoi(argv[5]); + if (argc > 6) split_label = atoi(argv[6]); + + vector all_files; + get_all_filenames(all_files, input_file_path + "\\*.octree"); + + //#pragma omp parallel for + for (int i = 0; i < all_files.size(); ++i) { + string filename = extract_filename(all_files[i]); + string filename_ouput = output_file_path + filename + "_output.octree"; + + adaptive_octree(all_files[i], filename_ouput); + + cout << filename + " done!\n"; + } + + return 0; + +} diff --git a/octree/tools/bbox.cpp b/octree/tools/bbox.cpp new file mode 100644 index 0000000..83a8425 --- /dev/null +++ b/octree/tools/bbox.cpp @@ -0,0 +1,50 @@ +#include +#include +#include + +#include "math_functions.h" +#include "filenames.h" +#include "points.h" +#include "cmd_flags.h" + +using namespace std; +using cflags::Require; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(type, kOptional, "box", "Choose from box and sphere"); + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: bbox.exe"); + return 0; + } + + string file_path = FLAGS_filenames; + vector all_files; + get_all_filenames(all_files, file_path); + + for (int i = 0; i < all_files.size(); i++) { + string filename = extract_filename(all_files[i]); + cout << filename << ", "; + + Points pts; + pts.read_points(all_files[i]); + int npt = pts.info().pt_num(); + const float* pt = pts.ptr(PointsInfo::kPoint); + + if (FLAGS_type == "box") { + float bbmin[3], bbmax[3]; + bouding_box(bbmin, bbmax, pt, npt); + cout << bbmin[0] << ", " << bbmin[1] << ", " << bbmin[2] << ", " + << bbmax[0] << ", " << bbmax[1] << ", " << bbmax[2] << endl; + } else { + float center[3], radius; + bounding_sphere(radius, center, pt, npt); + cout << center[0] << ", " << center[1] << ", " << center[2] << ", " + << radius << endl; + } + } + + return 0; +} diff --git a/octree/tools/build_octree.cpp b/octree/tools/build_octree.cpp new file mode 100644 index 0000000..7c2543c --- /dev/null +++ b/octree/tools/build_octree.cpp @@ -0,0 +1,173 @@ +#include +#include +#include +#include + +#include "cmd_flags.h" +#include "octree.h" +#include "filenames.h" +#include "math_functions.h" + +using std::vector; +using std::string; +using std::cout; +using std::endl; +using cflags::Require; +const float kPI = 3.14159265f; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_string(axis, kOptional, "y", "The upright axis of the input model"); +DEFINE_int(depth, kOptional, 6, "The maximum depth of the octree"); +DEFINE_int(full_depth, kOptional, 2, "The full layer of the octree"); +DEFINE_int(rot_num, kOptional, 12, "Number of poses rotated along the upright axis"); +DEFINE_float(offset, kOptional, 0.55f, "The offset value for handing thin shapes"); +DEFINE_bool(node_dis, kOptional, false, "Output per-node displacement"); +DEFINE_bool(node_feature, kOptional, false, "Compute per node feature"); +DEFINE_bool(split_label, kOptional, false, "Compute per node splitting label"); +DEFINE_bool(adaptive, kOptional, false, "Build adaptive octree"); +DEFINE_int(adp_depth, kOptional, 4, "The starting depth of adaptive octree"); +DEFINE_float(th_distance, kOptional, 2.0f, "The threshold for simplifying octree"); +DEFINE_float(th_normal, kOptional, 0.1f, "The threshold for simplifying octree"); +DEFINE_bool(key2xyz, kOptional, false, "Convert the key to xyz when serialization"); +DEFINE_bool(extrapolate, kOptional, false, "Exptrpolate the node feature"); +DEFINE_bool(save_pts, kOptional, false, "Save the average points as signal"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + + +// OctreeBuilder shows a basic example for building an octree with a point cloud +class OctreeBuilder { + public: + bool set_point_cloud(string filename) { + // load point cloud + bool succ = point_cloud_.read_points(filename); + if (!succ) { + cout << "Can not load " << filename << endl; + return false; + } + string msg; + succ = point_cloud_.info().check_format(msg); + if (!succ) { + cout << filename << endl << msg << endl; + return false; + } + + // deal with empty points + int npt = point_cloud_.info().pt_num(); + if (npt == 0) { + cout << "This is an empty points!" << endl; + return false; + } + cout << "Points number: " << npt < 1.0e-10f) { + float offset = FLAGS_offset * 2.0f * radius_ / float(1 << FLAGS_depth); + point_cloud_.displace(offset); + radius_ += offset; + } + + return true; + } + + void set_octree_info() { + octree_info_.initialize(FLAGS_depth, FLAGS_full_depth, FLAGS_node_dis, + FLAGS_node_feature, FLAGS_split_label, FLAGS_adaptive, FLAGS_adp_depth, + FLAGS_th_distance, FLAGS_th_normal, FLAGS_key2xyz, FLAGS_extrapolate, + FLAGS_save_pts, point_cloud_); + + // the point cloud has been centralized, + // so initializing the bbmin & bbmax in the following way + float bbmin[] = { -radius_, -radius_, -radius_ }; + float bbmax[] = { radius_, radius_, radius_ }; + octree_info_.set_bbox(bbmin, bbmax); +// std::cout << octree_info_.depth() << std::endl; + } + + void build_octree() { + octree_.build(octree_info_, point_cloud_); +// std::cout << point_cloud_.info + } + + void save_octree(const string& output_filename) { + // Modify the bounding box before saving, because the center of + // the point cloud is translated to (0, 0, 0) when building the octree + octree_.mutable_info().set_bbox(radius_, center_); + octree_.write_octree(output_filename); + } + + public: + Points point_cloud_; + float radius_, center_[3]; + OctreeInfo octree_info_; + Octree octree_; +}; + + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: Octree.exe"); + return 0; + } + + // file path + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + + #pragma omp parallel for + for (int i = 0; i < all_files.size(); i++) { + OctreeBuilder builder; + bool succ = builder.set_point_cloud(all_files[i]); + + string filename = extract_filename(all_files[i]); + if (!succ) { + if (FLAGS_verbose) cout << "Warning: " + filename + " is invalid!\n"; + continue; + } + builder.set_octree_info(); + + // data augmentation + float angle = 2.0f * kPI / float(FLAGS_rot_num); + float axis[] = { 0.0f, 0.0f, 0.0f }; + if (FLAGS_axis == "x") axis[0] = 1.0f; + else if (FLAGS_axis == "y") axis[1] = 1.0f; + else axis[2] = 1.0f; + + if (FLAGS_verbose) cout << "Processing: " + filename + "\n"; + for (int v = 0; v < FLAGS_rot_num; ++v) { + // output filename + char file_suffix[64]; + sprintf(file_suffix, "_%d_%d_%03d.octree", FLAGS_depth, FLAGS_full_depth, v); + + // build + builder.build_octree(); + + // save octree + builder.save_octree(output_path + filename + file_suffix); + + // rotate point for the next iteration + builder.point_cloud_.rotate(angle, axis); + + // message + //cout << "Processing: " << filename.substr(filename.rfind('\\') + 1) << endl; + } + } + + if (FLAGS_verbose) cout << "Done: " << FLAGS_filenames << endl; + return 0; +} + diff --git a/octree/tools/chamfer_distance.cpp b/octree/tools/chamfer_distance.cpp new file mode 100644 index 0000000..35b95d3 --- /dev/null +++ b/octree/tools/chamfer_distance.cpp @@ -0,0 +1,227 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "points.h" +#include "filenames.h" +#include "cmd_flags.h" + +using namespace std; + +DEFINE_string(path_a, kRequired, "", "The file path of a"); +DEFINE_string(path_b, kRequired, "", "The file path of b"); +DEFINE_string(filename_out, kRequired, "", "The output filename"); +DEFINE_int(pt_num, kOptional, -1, "The point num"); +DEFINE_string(category, kOptional, "", "The output filename"); + + +template < class VectorType, int DIM = 3, typename num_t = float, + class Distance = nanoflann::metric_L2, typename IndexType = size_t > +class KDTreeVectorOfVectorsAdaptor { + public: + typedef KDTreeVectorOfVectorsAdaptor self_t; + typedef typename Distance::template traits::distance_t metric_t; + typedef nanoflann::KDTreeSingleIndexAdaptor< metric_t, self_t, DIM, IndexType> index_t; + + //! The kd-tree index for the user to call its methods as usual with any other FLANN index. + index_t* index_; + + private: + const VectorType &data_; + + public: + // Constructor: takes a const ref to the vector of vectors object with the data points + KDTreeVectorOfVectorsAdaptor(const VectorType &mat, const int leaf_max_sz = 10) + : data_(mat) { + assert(mat.info().pt_num() != 0); + index_ = new index_t(DIM, *this, nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_sz)); + index_->buildIndex(); + } + + ~KDTreeVectorOfVectorsAdaptor() { + if (index_) delete index_; + } + + // Query for the num_closest closest points to a given point . + inline void query(const num_t *query_point, const size_t num_closest, IndexType *out_indices, + num_t *out_distances_sq, const int nChecks_IGNORED = 10) const { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances_sq); + index_->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); + } + + // Query for the closest points to a given point (entered as query_point[0:dim-1]). + inline void closest(const num_t *query_point, IndexType *out_index, num_t *out_distance_sq) const { + query(query_point, 1, out_index, out_distance_sq); + } + + // Interface expected by KDTreeSingleIndexAdaptor + const self_t & derived() const { return *this; } + self_t & derived() { return *this; } + + // Must return the number of data points + inline size_t kdtree_get_point_count() const { + return data_.info().pt_num(); + } + + // Returns the j'th component of the i'th point in the class: + inline num_t kdtree_get_pt(const size_t i, int j) const { + return data_.ptr(PointsInfo::kPoint)[i * DIM + j]; + } + + // Return false to default to a standard bbox computation loop. + template + bool kdtree_get_bbox(BBOX & /*bb*/) const { return false; } +}; + +void closet_pts(vector& idx, vector& distance, + const Points& pts, const Points& des) { + // build a kdtree + KDTreeVectorOfVectorsAdaptor kdtree(des); + + // kdtree search + size_t num = pts.info().pt_num(); + distance.resize(num); + idx.resize(num); + const float* pt = pts.ptr(PointsInfo::kPoint); + #pragma omp parallel for + for (int i = 0; i < num; ++i) { + kdtree.closest(pt + 3 * i, idx.data() + i, distance.data() + i); + } +} + + +std::default_random_engine generator_(static_cast(time(nullptr))); + +void downsample(Points& point_cloud, int target_num) { + int npt = point_cloud.info().pt_num(); + if (npt < target_num) return; + + std::bernoulli_distribution distribution_(float(target_num) / float(npt)); + vector pt, normal; + pt.reserve(npt); + normal.reserve(npt); + const float* ptr_pt = point_cloud.ptr(PointsInfo::kPoint); + const float* ptr_nm = point_cloud.ptr(PointsInfo::kNormal); + for (int i = 0; i < npt; ++i) { + if (distribution_(generator_)) { + for (int c = 0; c < 3; ++c) { + pt.push_back(ptr_pt[i * 3 + c]); + if (ptr_nm != nullptr) { + normal.push_back(ptr_nm[i * 3 + c]); + } + } + } + } + + if (normal.size() == 0) { + normal.assign(pt.size(), sqrtf(3.0f) / 3.0f); + } + + point_cloud.set_points(pt, normal); +} + +#if 1 + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: update_octree.exe"); + return 0; + } + + ofstream outfile(FLAGS_filename_out, ios::app); + if (!outfile) { + cout << "Error: Can not open the output file:" << FLAGS_filename_out << endl; + return 0; + } + + vector all_files_a, all_files_b; + get_all_filenames(all_files_a, FLAGS_path_a + "/*.points"); + get_all_filenames(all_files_b, FLAGS_path_b + "/*.points"); + std::cout << "File number a: " << all_files_a.size() << std::endl; + std::cout << "File number b: " << all_files_a.size() << std::endl; + + for (int i = 0; i < all_files_a.size(); ++i) { + // load points + string filename_a = all_files_a[i]; + string filename_b = all_files_b[i]; + Points point_cloud_a, point_cloud_b; + point_cloud_a.read_points(filename_a); + point_cloud_b.read_points(filename_b); + + // random downsample points + if (FLAGS_pt_num > 0) { + downsample(point_cloud_a, FLAGS_pt_num); + downsample(point_cloud_b, FLAGS_pt_num); + + point_cloud_a.write_points(FLAGS_filename_out + "_a.points"); + point_cloud_b.write_points(FLAGS_filename_out + "_b.points"); + } + + // distance from a to b + vector idx; + vector distance; + closet_pts(idx, distance, point_cloud_a, point_cloud_b); + + float avg_ab = 0; + for (auto& d : distance) { avg_ab += d; } + avg_ab /= (float)distance.size(); + + // distance from b to a + closet_pts(idx, distance, point_cloud_b, point_cloud_a); + + float avg_ba = 0; + for (auto& d : distance) { avg_ba += d; } + avg_ba /= (float)distance.size(); + + // output to file + string filename = extract_filename(filename_a); // no suffix + cout << "Processing: " + filename + "\n"; + outfile << FLAGS_category << ", " << filename << ", " + << extract_filename(filename_b) << ", " + << point_cloud_a.info().pt_num() << ", " + << point_cloud_b.info().pt_num() << ", " + << avg_ab << ", " << avg_ba << ", " + << avg_ab + avg_ba << endl; + } + + outfile.close(); + + return 0; +} + +#else + +int main(int argc, char* argv[]) { + + // load points + vector feature{ 1, 1, 1 }; + vector pt_src{ 2.5, 2.5, 2.5, 1, 1, 0, -1, 0, 0 }; + vector pt_des{ 0, 0, 0, 1, 1, 1, 2, 2, 2 }; + Points point_cloud_a, point_cloud_b; + point_cloud_a.set_points(pt_src, vector(), feature); + point_cloud_b.set_points(pt_des, vector(), feature); + + // cloest distance + vector idx; + vector distance; // squared distance + closet_pts(idx, distance, point_cloud_a, point_cloud_b); + + // average + float avg_ab = 0; + for (auto& d : distance) { avg_ab += d; } + avg_ab /= (float)distance.size(); + + // output + string filename = extract_filename(FLAGS_filename_src); + cout << filename + to_string(avg_ab) << endl; + + return 0; +} + +#endif \ No newline at end of file diff --git a/octree/tools/check_octree.cpp b/octree/tools/check_octree.cpp new file mode 100644 index 0000000..6e03c38 --- /dev/null +++ b/octree/tools/check_octree.cpp @@ -0,0 +1,96 @@ +#include +#include +#include + +#include "filenames.h" +#include "cmd_flags.h" +#include "octree.h" + +using namespace std; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: check_octree.exe"); + return 0; + } + + vector all_files; + get_all_filenames(all_files, FLAGS_filenames); + + for (int i = 0; i < all_files.size(); i++) { + cout << "\n===============" << endl; + + // get filename + size_t pos = all_files[i].rfind('\\') + 1; + string filename = all_files[i].substr(pos); + cout << filename << " infomation:" << std::endl; + + // load octree + Octree octree; + bool succ = octree.read_octree(all_files[i]); + if (!succ) { + cout << "Can not load " << filename << std::endl; + continue; + } + + // check format + string msg; + succ = octree.info().check_format(msg); + if (!succ) { + cout << filename << std::endl << msg << std::endl; + continue; + } else { + cout << "This is a valid octree!" << endl; + } + + // output info + int depth = octree.info().depth(); + cout << "magic_str:" << OctreeInfo::kMagicStr << endl; + cout << "batch_size: " << octree.info().batch_size() << endl; + cout << "depth: " << octree.info().depth() << endl; + cout << "full_layer: " << octree.info().full_layer() << endl; + cout << "adaptive_layer: " << octree.info().adaptive_layer() << endl; + cout << "threshold_distance: " << octree.info().threshold_distance() << endl; + cout << "threshold_normal: " << octree.info().threshold_normal() << endl; + cout << "is_adaptive: " << octree.info().is_adaptive() << endl; + cout << "has_displace: " << octree.info().has_displace() << endl; + cout << "nnum: "; + for (int d = 0; d < 16; ++d) { + cout << octree.info().node_num(d) << " "; + } + cout << endl << "nnum_cum: "; + for (int d = 0; d < 16; ++d) { + cout << octree.info().node_num_cum(d) << " "; + } + cout << endl << "nnum_nempty: "; + for (int d = 0; d < 16; ++d) { + cout << octree.info().node_num_nempty(d) << " "; + } + cout << endl << "total_nnum: " << octree.info().total_nnum() << endl; + cout << "total_nnum_capacity: " << octree.info().total_nnum_capacity() << endl; + cout << "channel: "; + for (int j = 0; j < OctreeInfo::kPTypeNum; ++j) { + cout << octree.info().channel(static_cast(1 << j)) << " "; + } + cout << endl << "locations: "; + for (int j = 0; j < OctreeInfo::kPTypeNum; ++j) { + cout << octree.info().locations(static_cast(1 << j)) << " "; + } + cout << endl << "bbox_max: "; + for (int j = 0; j < 3; ++j) { + cout << octree.info().bbmax()[j] << " "; + } + cout << endl << "bbox_min: "; + for (int j = 0; j < 3; ++j) { + cout << octree.info().bbmin()[j] << " "; + } + cout << "\nkey2xyz: " << octree.info().is_key2xyz() << endl; + cout << "sizeof_octree: " << octree.info().sizeof_octree() << endl; + cout << "===============\n" << endl; + } + + return 0; +} diff --git a/octree/tools/clip_points.cpp b/octree/tools/clip_points.cpp new file mode 100644 index 0000000..b960936 --- /dev/null +++ b/octree/tools/clip_points.cpp @@ -0,0 +1,45 @@ +#include + +#include "points.h" +#include "filenames.h" +#include "cmd_flags.h" + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_float(bbmin, kOptional, -1.0, "The bottom left corner of the bounding box"); +DEFINE_float(bbmax, kOptional, 1.0, "The top right corner of the bounding box"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + +using std::cout; + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: clip_points.exe"); + return 0; + } + + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + float bbmin[3] = {FLAGS_bbmin, FLAGS_bbmin, FLAGS_bbmin }; + float bbmax[3] = {FLAGS_bbmax, FLAGS_bbmax, FLAGS_bbmax }; + + vector all_files; + get_all_filenames(all_files, file_path); + for (int i = 0; i < all_files.size(); i++) { + Points pts; + pts.read_points(all_files[i]); + pts.clip(bbmin, bbmax); + + // get filename + string filename = extract_filename(all_files[i]); + if (FLAGS_verbose) cout << "Processing: " << filename << std::endl; + pts.write_points(output_path + filename + ".clip.points"); + } + + return 0; +} \ No newline at end of file diff --git a/octree/tools/custom_data.cpp b/octree/tools/custom_data.cpp new file mode 100644 index 0000000..4d8b108 --- /dev/null +++ b/octree/tools/custom_data.cpp @@ -0,0 +1,50 @@ +#include +#include + +#include "points.h" +#include "filenames.h" +#include "cmd_flags.h" + + +DEFINE_string(output_path, kOptional, ".", "The output path"); + +using std::cout; +using std::vector; + + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: custom_points"); + return 0; + } + + Points point_cloud; + vector points, normals, features, labels; + // Set your data in points, normals, features, and labels. + // The points must not be empty, the labels may be empty, + // the normals & features must not be empty at the same time. + // points: 3 channels, x_1, y_1, z_1, ..., x_n, y_n, z_n + // normals: 3 channels, nx_1, ny_1, nz_1, ..., nx_n, ny_n, nz_n + // features (such as RGB color): k channels, r_1, g_1, b_1, ..., r_n, g_n, b_n + // labels: 1 channels, per-points labels + + // For example: + // The following array contains two points: (1.0, 2.0, 3.0) and (4.0, 5.0, 6.0) + float pts[] = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 }; + points.assign(pts, pts + 6); + // Their normals are (1.0, 0.0, 0.0) and (0.57735, 0.57735, 0.57735) + float ns[] = { 1.0, 0.0, 0.0, 0.57735, 0.57735, 0.57735 }; + normals.assign(ns, ns + 6); + // They may also have 4 channel colors (0.5, 0.5, 0.5, 1.0) and (1.0, 0, 0, 0, 0.8) + float rgba[] = { 0.5, 0.5, 0.5, 1.0, 1.0, 0, 0, 0, 0.8 }; + features.assign(rgba, rgba + 8); + // Their labels are 0 and 1 respectively + float lb[] = { 0.0, 1.0 }; + labels.assign(lb, lb + 2); + + point_cloud.set_points(points, normals, features, labels); + point_cloud.write_points(FLAGS_output_path + "\\my_points.points"); + + return 0; +} \ No newline at end of file diff --git a/octree/tools/merge_octree.cpp b/octree/tools/merge_octree.cpp new file mode 100644 index 0000000..bcc31b2 --- /dev/null +++ b/octree/tools/merge_octree.cpp @@ -0,0 +1,65 @@ +#include +#include + +#include "octree.h" +#include "merge_octrees.h" +#include "cmd_flags.h" +#include "filenames.h" + +using namespace std; +using cflags::Require; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_int(num, kOptional, 2, "The number of octrees to be merged"); + + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: merge_octrees"); + return 0; + } + + // file path + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + + //#pragma omp parallel for + for (int i = 0; i < all_files.size() / FLAGS_num; i ++) { + // load FLAGS_num octrees + vector octrees_in(FLAGS_num); + vector octrees(FLAGS_num); + for (int j = 0; j < FLAGS_num; ++j) { + string filename = all_files[i * FLAGS_num + j]; + bool succ = octrees[j].read_octree(filename); + if (!succ) { + cout << "Can not load: " << filename << endl; + continue; + } + octrees_in[j] = octrees[j].ptr_raw_cpu(); + } + + // merge + vector octree_out; + merge_octrees(octree_out, octrees_in); + + // save + char buffer[64]; + sprintf(buffer, "batch_%03d.octree", i); + cout << "Processing: " << buffer << endl; + string filename = output_path + buffer; + ofstream outfile(filename, ios::binary); + outfile.write(octree_out.data(), octree_out.size()); + outfile.close(); + } + + cout << "Done: " << FLAGS_filenames << endl; + return 0; +} \ No newline at end of file diff --git a/octree/tools/mesh2points.cpp b/octree/tools/mesh2points.cpp new file mode 100644 index 0000000..9292588 --- /dev/null +++ b/octree/tools/mesh2points.cpp @@ -0,0 +1,123 @@ +#include "mesh.h" + +#include +#include +#include +#include "points.h" +#include "filenames.h" +#include "cmd_flags.h" +#include "math_functions.h" + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_float(area_unit, kOptional, 1.0, "The area unit used to sample points"); +DEFINE_float(scale, kOptional, 1.0, "The scale the mesh before sampling"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + +using std::cout; + +std::default_random_engine generator(static_cast(time(nullptr))); +std::uniform_real_distribution distribution(0.0, 1.0); + +void sample_points(vector& pts, vector& normals, + const vector& V, const vector& F, float area_unit) { + vector face_normal, face_center, face_area; + compute_face_normal(face_normal, face_area, V, F); + compute_face_center(face_center, V, F); + + //float avg_area = 0; + //for (auto& a : face_area) { avg_area += a;} + //avg_area /= face_area.size(); + //area_unit *= avg_area; + if (area_unit <= 0) area_unit = 1.0e-5f; + + int nf = F.size() / 3; + vector point_num(nf); + int total_pt_num = 0; + for (int i = 0; i < nf; ++i) { + int n = static_cast(face_area[i] / area_unit + 0.5f); + if (n < 1) n = 1; // sample at least one point + //if (n > 100) n = 100; + point_num[i] = n; + total_pt_num += n; + } + + pts.resize(3 * total_pt_num); + normals.resize(3 * total_pt_num); + for (int i = 0, id = 0; i < nf; ++i) { + int ix3 = i * 3, idx3 = id * 3; + for (int k = 0; k < 3; ++k) { + pts[idx3 + k] = face_center[ix3 + k]; + normals[idx3 + k] = face_normal[ix3 + k]; + } + + for (int j = 1; j < point_num[i]; ++j) { + float x = 0, y = 0, z = 0; + while (z < 0.0001 || z > 0.9999) { + x = distribution(generator); + y = distribution(generator); + z = 1.0 - x - y; + } + idx3 = (id + j) * 3; + int f0x3 = F[ix3] * 3, f1x3 = F[ix3 + 1] * 3, f2x3 = F[ix3 + 2] * 3; + for (int k = 0; k < 3; ++k) { + pts[idx3 + k] = x * V[f0x3 + k] + y * V[f1x3 + k] + z * V[f2x3 + k]; + normals[idx3 + k] = face_normal[ix3 + k]; + + } + } + id += point_num[i]; + } +} + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: mesh2points.exe"); + return 0; + } + + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + for (int i = 0; i < all_files.size(); i++) { + // get filename + string filename = extract_filename(all_files[i]); + if (FLAGS_verbose) cout << "Processing: " << filename << std::endl; + + // load mesh + vector V; + vector F; + read_mesh(all_files[i], V, F); + + // scale mesh + float radius = 1.0, center[3]; + if (FLAGS_scale != 1.0f) { + bounding_sphere(radius, center, V.data(), V.size() / 3); + float scale = FLAGS_scale / (2 * radius); + for (auto& v : V) { v *= scale; } + } + + // sample points + vector pts, normals; + sample_points(pts, normals, V, F, FLAGS_area_unit); + + // scale points + if (FLAGS_scale != 1.0f) { + float scale = 2 * radius / FLAGS_scale; + for (auto& p : pts) { p *= scale; } + } + + // save points + Points point_cloud; + point_cloud.set_points(pts, normals); + point_cloud.write_points(output_path + filename + ".points"); + } + + return 0; +} \ No newline at end of file diff --git a/octree/tools/octree2mesh.cpp b/octree/tools/octree2mesh.cpp new file mode 100644 index 0000000..38832e9 --- /dev/null +++ b/octree/tools/octree2mesh.cpp @@ -0,0 +1,77 @@ +#include +#include +#include +#include + +#include "mesh.h" +#include "octree.h" +#include "contour.h" +#include "cmd_flags.h" +#include "filenames.h" + +using namespace std; +using cflags::Require; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_int(depth_start, kOptional, 0, "The starting depth"); +DEFINE_int(depth_end, kOptional, 10, "The ending depth"); +DEFINE_bool(pu, kOptional, false, "Partition of Unity"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: octree2mesh.exe"); + return 0; + } + + // file path + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + + for (int i = 0; i < all_files.size(); i++) { + // get filename + string filename = extract_filename(all_files[i]); + if (FLAGS_verbose) cout << "Processing: " << filename << std::endl; + + // load octree + Octree octree; + bool succ = octree.read_octree(all_files[i]); + if (!succ) { + cout << "Can not load " << filename << std::endl; + continue; + } + string msg; + succ = octree.info().check_format(msg); + if (!succ) { + cout << filename << ": format error!\n" << msg << std::endl; + continue; + } + + // convert + vector V; vector F; + if (!FLAGS_pu) { + octree.octree2mesh(V, F, FLAGS_depth_start, FLAGS_depth_end); + } else { + clock_t t = clock(); + Contour contour(&octree); + contour.marching_cube(V, F); + t = clock() - t; + cout << "time : " << t << endl; + } + + // save points + filename = output_path + filename + ".obj"; + write_obj(filename, V, F); + } + + return 0; +} diff --git a/octree/tools/octree2points.cpp b/octree/tools/octree2points.cpp new file mode 100644 index 0000000..faacf42 --- /dev/null +++ b/octree/tools/octree2points.cpp @@ -0,0 +1,71 @@ +#include +#include +#include + +#include "filenames.h" +#include "points.h" +#include "octree.h" +#include "cmd_flags.h" + +using namespace std; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_string(suffix, kOptional, "points", "The output file suffix"); +DEFINE_int(depth_start, kOptional, 0, "The starting depth"); +DEFINE_int(depth_end, kOptional, 10, "The ending depth"); +DEFINE_int(rescale, kOptional, true, "The ending depth"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: octree2points.exe"); + return 0; + } + + // file path + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + + for (int i = 0; i < all_files.size(); i++) { + string filename = extract_filename(all_files[i]);; + if (FLAGS_verbose) cout << "Processing: " << filename << std::endl; + + // load octree + Octree octree; + bool succ = octree.read_octree(all_files[i]); + if (!succ) { + if (FLAGS_verbose) cout << "Can not load " << filename << std::endl; + continue; + } + string msg; + succ = octree.info().check_format(msg); + if (!succ) { + if (FLAGS_verbose) cout << filename << std::endl << msg << std::endl; + continue; + } + + // convert + Points pts; + octree.octree2pts(pts, FLAGS_depth_start, FLAGS_depth_end, FLAGS_rescale); + + // save points + if (FLAGS_suffix == "ply") { + filename = output_path + filename + ".ply"; + pts.write_ply(filename); + } else { + filename = output_path + filename + ".points"; + pts.write_points(filename); + } + } + + return 0; +} diff --git a/octree/tools/octree2pts_suncg.cpp b/octree/tools/octree2pts_suncg.cpp new file mode 100644 index 0000000..284529d --- /dev/null +++ b/octree/tools/octree2pts_suncg.cpp @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include + +#include "filenames.h" +#include "points.h" +#include "octree.h" +#include "cmd_flags.h" + +using namespace std; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + +void octree2pts(vector& pts, vector& labels, const Octree& octree) { + const int depth = octree.info().depth(); + const int num = octree.info().node_num(depth); + + const int* child = octree.children_cpu(depth); + const float* label = octree.label_cpu(depth); + const float* normal = octree.feature_cpu(depth); + const unsigned int* key = octree.key_cpu(depth); + + pts.clear(); labels.clear(); + for (int i = 0; i < num; ++i) { + float n[3] = { normal[i], normal[i + num], normal[i + 2 * num] }; + float len = fabs(n[0]) + fabs(n[1]) + fabs(n[2]); + if (len == 0) continue; + + int pt[3]; + octree.key2xyz(pt, key[i], depth); + for (int c = 0; c < 3; ++c) { + pts.push_back(pt[c]); + } + labels.push_back(static_cast(label[i])); + } +} + + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: octree2pts_suncg.exe"); + return 0; + } + + // file path + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + for (int i = 0; i < all_files.size(); i++) { + string filename = extract_filename(all_files[i]);; + if (FLAGS_verbose) cout << "Processing: " << filename << std::endl; + + // load octree + Octree octree; + bool succ = octree.read_octree(all_files[i]); + if (!succ) { + if (FLAGS_verbose) cout << "Can not load " << filename << std::endl; + continue; + } + string msg; + succ = octree.info().check_format(msg); + if (!succ) { + if (FLAGS_verbose) cout << filename << std::endl << msg << std::endl; + continue; + } + + // convert + vector pts, labels; + octree2pts(pts, labels, octree); + + // write + filename = output_path + filename + ".suncg"; + ofstream outfile(filename, ios::binary); + if (!outfile) { + cout << "Can not open :" << filename << endl; + continue; + } + + int num = labels.size(); + outfile.write((char*)(&num), sizeof(int)); + outfile.write((char*)pts.data(), sizeof(int) * 3 * num); + outfile.write((char*)labels.data(), sizeof(int) * num); + + outfile.close(); + } + + return 0; +} diff --git a/octree/tools/octree_dropout.cpp b/octree/tools/octree_dropout.cpp new file mode 100644 index 0000000..4a8e3e4 --- /dev/null +++ b/octree/tools/octree_dropout.cpp @@ -0,0 +1,70 @@ +#include +#include +#include +#include + +#include "filenames.h" +#include "points.h" +#include "octree.h" +#include "contour.h" +#include "cmd_flags.h" + +using namespace std; +using cflags::Require; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_int(depth_dropout, kOptional, 2, "The dropout depth"); +DEFINE_float(dropout_ratio, kOptional, 0.5, "The dropout ratio"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: octree_dropout"); + return 0; + } + + // file path + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + + char suffix[128]; + sprintf(suffix, ".drop_%d_%.2f.octree", FLAGS_depth_dropout, FLAGS_dropout_ratio); + for (int i = 0; i < all_files.size(); i++) { + // get filename + string filename = extract_filename(all_files[i]); + if (FLAGS_verbose) cout << "Processing: " << filename << std::endl; + + //// load octree + //Octree octree; + //bool succ = octree.read_octree(all_files[i]); + //if (!succ) { + // cout << "Can not load " << filename << std::endl; + // continue; + //} + //string msg; + //succ = octree.info().check_format(msg); + //if (!succ) { + // cout << filename << ": format error!\n" << msg << std::endl; + // continue; + //} + + //// dropout + //Octree octree_output; + //octree.dropout(octree_output, FLAGS_depth_dropout, FLAGS_dropout_ratio); + + //// save points + //string filename_output = output_path + filename + suffix; + //octree_output.write_octree(filename_output); + } + + return 0; +} diff --git a/octree/tools/octree_prune.cpp b/octree/tools/octree_prune.cpp new file mode 100644 index 0000000..9779d65 --- /dev/null +++ b/octree/tools/octree_prune.cpp @@ -0,0 +1,176 @@ +#include +#include +#include +#include +#include +#include + +#include "filenames.h" +#include "points.h" +#include "octree.h" +#include "contour.h" +#include "cmd_flags.h" + +using namespace std; +using cflags::Require; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_string(axis, kOptional, "y", "The upright axis of the input model"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + +// !!! todo: merge the code with octree zbuffer + +void prune_octree(Octree& octree_out, const Octree& octree_in) { + const int bnd0[7][3] = { { 1, 1, 1 }, + { 2, 2, 2 }, { 4, 4, 4 }, { 8, 6, 8 }, + { 16, 10, 16 }, { 30, 18, 30 }, { 60, 36, 60 } + }; + const int bnd1[7][3] = { { 1, 1, 1 }, + { 2, 2, 2 }, { 4, 3, 4 }, { 8, 5, 8 }, + { 15, 9, 15 }, { 30, 18, 30 }, { 60, 36, 60 } + }; + + // 0, 1, 2, 3, 4, 5, 6 + vector nnum_prune = { 1, 8, 64, 384, 2560, 16200, 129600 }; + vector nnum_nempty_prune = { 1, 8, 48, 320, 2025, 16200, 129600 }; + + // for the full layer + int depth = octree_in.info().depth(); + int depth_full = octree_in.info().full_layer(); + // CHECK(depth_full > 1); + + // calculate the node number for the octree_out + vector nnum(depth + 1, 0), nnum_nempty(depth + 1, 0); + for (int d = 0; d <= depth; ++d) { + nnum[d] = d > depth_full ? octree_in.info().node_num(d) : nnum_prune[d]; + nnum_nempty[d] = d >= depth_full ? octree_in.info().node_num_nempty(d) : nnum_nempty_prune[d]; + } + + // init + OctreeInfo info_out = octree_in.info(); + info_out.set_nnum(nnum.data()); + info_out.set_nempty(nnum_nempty.data()); + info_out.set_nnum_cum(); + info_out.set_ptr_dis(); + info_out.set_full_layer(2); // !!! full layer + octree_out.resize_octree(info_out.sizeof_octree()); + octree_out.mutable_info() = info_out; + + int channel_feature = octree_in.info().channel(OctreeInfo::kFeature); + int location_feature = octree_in.info().locations(OctreeInfo::kFeature); + int channel_label = octree_in.info().channel(OctreeInfo::kLabel); + int location_label = octree_in.info().locations(OctreeInfo::kLabel); + int channel_split = octree_in.info().channel(OctreeInfo::kSplit); + int location_split = octree_in.info().locations(OctreeInfo::kSplit); + + for (int d = 1; d <= depth_full; ++d) { + int id = 0, j = 0; + int* child_out = octree_out.mutable_children_cpu(d); + const int* child_in = octree_in.children_cpu(d); + unsigned int* key_out = octree_out.mutable_key_cpu(d); + float* split_out = octree_out.mutable_split_cpu(d); + + for (int i = 0; i < octree_in.info().node_num(d); ++i) { + unsigned int key = i, pt[3]; + octree_in.key2xyz(pt, key, d); + if (pt[0] < bnd0[d][0] && pt[1] < bnd0[d][1] && + pt[2] < bnd0[d][2]) { + if (pt[0] < bnd1[d][0] && pt[1] < bnd1[d][1] && + pt[2] < bnd1[d][2]) { + child_out[j] = child_in[i] < 0 ? -1 : id++; + } else { + child_out[j] = -1; // empty + } + + key_out[j] = key; + + if ((location_split == -1 || d == depth) && channel_split != 0) { + split_out[j] = child_out[j] < 0 ? 0.0f : 1.0f; + } + + // update j + j++; + } + } + } + + + // copy directly + for (int d = depth; d > depth_full; --d) { + int num = octree_in.info().node_num(d); + + const int* child_in = octree_in.children_cpu(d); + int* child_out = octree_out.mutable_children_cpu(d); + std::copy_n(child_in, num, child_out); + + const unsigned int* key_in = octree_in.key_cpu(d); + unsigned int* key_out = octree_out.mutable_key_cpu(d); + std::copy_n(key_in, num, key_out); + + if (location_feature == -1 || d == depth) { + const float * feature_in = octree_in.feature_cpu(d); + float* feature_out = octree_out.mutable_feature_cpu(d); + std::copy_n(feature_in, num * channel_feature, feature_out); + } + + if ((location_label == -1 || d == depth) && channel_label != 0) { + const float * label_in = octree_in.label_cpu(d); + float* label_out = octree_out.mutable_label_cpu(d); + std::copy_n(label_in, num, label_out); + } + + if ((location_split == -1 || d == depth) && channel_split != 0) { + const float * split_in = octree_in.split_cpu(d); + float* split_out = octree_out.mutable_split_cpu(d); + std::copy_n(split_in, num, split_out); + } + } +} + + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: octree2mesh.exe"); + return 0; + } + + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + for (int i = 0; i < all_files.size(); i++) { + // get filename + string filename = extract_filename(all_files[i]); + if (FLAGS_verbose) cout << "Processing: " << filename << std::endl; + + // load octree + Octree octree_in; + bool succ = octree_in.read_octree(all_files[i]); + if (!succ) { + cout << "Can not load " << filename << std::endl; + continue; + } + string msg; + succ = octree_in.info().check_format(msg); + if (!succ) { + cout << filename << ": format error!\n" << msg << std::endl; + continue; + } + + // prune full layer + Octree octree_out; + prune_octree(octree_out, octree_in); + + // save octree + string filename_output = output_path + filename + ".prune.octree"; + octree_out.write_octree(filename_output); + } + + return 0; +} diff --git a/octree/tools/octree_samples.cpp b/octree/tools/octree_samples.cpp new file mode 100644 index 0000000..ee177fb --- /dev/null +++ b/octree/tools/octree_samples.cpp @@ -0,0 +1,38 @@ +#include +#include + +#include "octree_samples.h" +#include "filenames.h" +#include "cmd_flags.h" + + +DEFINE_string(output_path, kRequired, ".", "The output path"); + +using std::cout; + + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: octree_samples.exe"); + return 0; + } + + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + output_path += "/"; + + for (int i = 1; i < 7; i++) { + string filename = "octree_" + std::to_string(i); + size_t size = 0; + const char* ptr = (const char*)octree::get_one_octree(filename.c_str(), &size); + + std::ofstream outfile(output_path + filename + ".octree", std::ios::binary); + outfile.write(ptr, size); + outfile.close(); + + cout << "Save: " << filename << std::endl; + } + + return 0; +} \ No newline at end of file diff --git a/octree/tools/octree_zbuffer.cpp b/octree/tools/octree_zbuffer.cpp new file mode 100644 index 0000000..50e171c --- /dev/null +++ b/octree/tools/octree_zbuffer.cpp @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include +#include + +#include "octree.h" +#include "filenames.h" +#include "cmd_flags.h" +#include "math_functions.h" +#include "transform_octree.h" + +using namespace std; +using cflags::Require; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_string(axis, kOptional, "y", "The upright axis of the input model"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + +class RandAxis { + public: + RandAxis(float upright[3]) + : generator_(static_cast(time(nullptr))), + distribution_(-1.0, 1.0) { + for (int i = 0; i < 3; ++i) { upright_dir_[i] = upright[i]; } + } + + void operator()(float* axis) { + float dot = 1.0f; + while (dot > 0) { + for (int i = 0; i < 3; ++i) { axis[i] = distribution_(generator_); } + float len = norm2(axis, 3); + if (len < 1.0e-6) continue; // ignore zero vector + for (int i = 0; i < 3; ++i) { axis[i] /= len; } + dot = dot_prod(axis, upright_dir_); + } + } + + private: + float upright_dir_[3]; + std::default_random_engine generator_; + std::uniform_real_distribution distribution_; +}; + + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: octree2mesh.exe"); + return 0; + } + + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + float upright[] = { 0.0f, 0.0f, 0.0f }; + if (FLAGS_axis == "x") upright[0] = 1.0f; + else if (FLAGS_axis == "y") upright[1] = 1.0f; + else upright[2] = 1.0f; + RandAxis rand_axis(upright); + + vector all_files; + get_all_filenames(all_files, file_path); + for (int i = 0; i < all_files.size(); i++) { + // get filename + string filename = extract_filename(all_files[i]); + if (FLAGS_verbose) cout << "Processing: " << filename << std::endl; + + // load octree + Octree octree; + bool succ = octree.read_octree(all_files[i]); + if (!succ) { + cout << "Can not load " << filename << std::endl; + continue; + } + string msg; + succ = octree.info().check_format(msg); + if (!succ) { + cout << filename << ": format error!\n" << msg << std::endl; + continue; + } + + // dropout + ScanOctree zbuffer; + vector octree_out; + vector axis = { 0, 0, 1, 0, 0, 1 }; + rand_axis(axis.data()); + rand_axis(axis.data() + 3); + zbuffer.scan(octree_out, octree, axis); + + // save octree + string filename_output = output_path + filename + ".zbuffer.octree"; + std::ofstream outfile(filename_output, std::ios::binary); + outfile.write(octree_out.data(), octree_out.size()); + outfile.close(); + } + + return 0; +} diff --git a/octree/tools/pdb_to_points.cpp b/octree/tools/pdb_to_points.cpp new file mode 100644 index 0000000..4098c4b --- /dev/null +++ b/octree/tools/pdb_to_points.cpp @@ -0,0 +1,350 @@ +#include +#include + +#include "points.h" +#include "filenames.h" +#include "cmd_flags.h" + +DEFINE_string(complex_id, kRequired, "", "The file include input complex_id"); +DEFINE_string(output_path, kOptional, "./", "The output path"); + +using std::cout; +using std::vector; + + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: custom_points"); + return 0; + } +// auto& flag_map = FlagRegistry::Registry(); +// string complex_id = flag_map.find("complex_id"); + + + Points point_cloud; + vector pts, normals, features, labels; + + // read ID_name from file: + std::ifstream infile0(FLAGS_complex_id); + string ID_name; + std::getline(infile0, ID_name); + infile0.close(); + + std::cout << "the complex id is: " << ID_name << "." << std::endl; + string ligand_file_name = "./" + ID_name + "_cdk_ligand.xyz"; + std::ifstream infile(ligand_file_name); + + int lig_num = 0; + int i; + float x, y, z; + float center[3] = {0,0,0}; + std::string line; + std::string ele1, ele2, ele3; + std::string delimiter = " "; + while (infile >> ele1 >> ele2 >> ele3) + { + x = std::stof(ele1); + y = std::stof(ele2); + z = std::stof(ele3); + + lig_num += 1; + + pts.push_back(x); pts.push_back(y); pts.push_back(z); + + center[0] += x; + center[1] += y; + center[2] += z; + } + +// std::cout << center[0] << center[1] << center[2] << lig_num << std::endl; + std::cout << "ligand point number: " << lig_num << std::endl; + + center[0] = center[0] / lig_num; + center[1] = center[1] / lig_num; + center[2] = center[2] / lig_num; + std::cout << "center is: " << center[0] << " " << center[1] << " " << center[2] << std::endl; + + infile.close(); + + std::ofstream outputFile; + outputFile.open("center.txt"); + outputFile << center[0] << " " << center[1] << " " << center[2] ; + outputFile.close(); + + + string pocket_file_name = "./" + ID_name + "_cdk_pocket.xyz"; + std::ifstream infile2(pocket_file_name); + + int pro_num = 0; + while (infile2 >> ele1 >> ele2 >> ele3) + { + //std::cout << ele1 << " " << ele2 << " " << ele3 << ". "; + x = std::stof(ele1); + y = std::stof(ele2); + z = std::stof(ele3); + + pro_num++; + + //i = n - 1; + pts.push_back(x); pts.push_back(y); pts.push_back(z); + + } + std::cout << "last value in pts: " << pts[pts.size()-1] << std::endl; + + infile2.close(); + std::cout << "pocket point number: " << pro_num << std::endl; + + + + + // QQ: create dictionary{id: feature} according to index, the index start from 1, same as mol2 file and pdb file. + string ligand_feature_file_name = "./" + ID_name + "_ligand_feature.txt"; + std::ifstream infile3(ligand_feature_file_name); + std::map> ligand_feature_dic; + std::string ele, ele0, ele4, ele5, ele6, ele7, ele8, ele9, ele10, ele11, ele12, ele13, + ele14, ele15, ele16, ele17, ele18, ele19, ele20, ele21, ele22, ele23, ele24; + + int idx; + while (infile3 >> ele0 >> ele1 >> ele2 >> ele3 >> ele4 >> ele5 >> ele6 >> ele7 >> ele8 >> ele9 >> ele10 >> + ele11 >> ele12 >> ele13 >> ele14 >> ele15 >> ele16 >> ele17 >> ele18 >> ele19 >> ele20 >> + ele21 >> ele22 >> ele23 >> ele24) + { +// std::cout << ele1 << " " << ele2 << " " << ele3 << " " << ele4 << " " << ele5 <<" " << ele6<< std::endl; + idx = std::stoi(ele0); +// std::cout << "index is: " << idx << '\n'; + + vector feature; + feature.clear(); +// vdwrad = std::stof(ele18); + x = std::stof(ele1); + y = std::stof(ele2); + z = std::stof(ele3); + +// std::cout << "x,y,z is " << x << " " << y << " "<< z << std::endl; + feature.push_back(x); feature.push_back(y); feature.push_back(z); + + feature.push_back(std::stof(ele4)); + feature.push_back(std::stof(ele5)); + feature.push_back(std::stof(ele6)); + feature.push_back(std::stof(ele7)); + feature.push_back(std::stof(ele8)); + feature.push_back(std::stof(ele9)); + feature.push_back(std::stof(ele10)); + feature.push_back(std::stof(ele11)); + feature.push_back(std::stof(ele12)); + feature.push_back(std::stof(ele13)); + feature.push_back(std::stof(ele14)); + feature.push_back(std::stof(ele15)); + feature.push_back(std::stof(ele16)); + feature.push_back(std::stof(ele17)); + feature.push_back(std::stof(ele18)); + feature.push_back(std::stof(ele19)); + feature.push_back(std::stof(ele20)); + feature.push_back(std::stof(ele21)); + feature.push_back(std::stof(ele22)); + feature.push_back(std::stof(ele23)); + feature.push_back(std::stof(ele24)); + + ligand_feature_dic[idx] = feature; + +// for(int i =0; i< 24; i++){std::cout << i << " " << feature[i] << " ";} +// std::cout << std::endl; +// std::cout << "feature size: " << feature.size() <> pocket_feature_dic; + + while (infile4 >> ele0 >> ele1 >> ele2 >> ele3 >> ele4 >> ele5 >> ele6 >> ele7 >> ele8 >> ele9 >> ele10 >> + ele11 >> ele12 >> ele13 >> ele14 >> ele15 >> ele16 >> ele17 >> ele18 >> ele19 >> ele20 >> + ele21 >> ele22 >> ele23 >> ele24) + { + //std::cout << ele1 << " " << ele2 << " " << ele3 << ". "; + idx = std::stoi(ele0); +// std::cout << "index is: " << idx << '\n'; + + vector feature; + x = std::stof(ele1); + y = std::stof(ele2); + z = std::stof(ele3); + + feature.push_back(x); feature.push_back(y); feature.push_back(z); + + feature.push_back(std::stof(ele4)); + feature.push_back(std::stof(ele5)); + feature.push_back(std::stof(ele6)); + feature.push_back(std::stof(ele7)); + feature.push_back(std::stof(ele8)); + feature.push_back(std::stof(ele9)); + feature.push_back(std::stof(ele10)); + feature.push_back(std::stof(ele11)); + feature.push_back(std::stof(ele12)); + feature.push_back(std::stof(ele13)); + feature.push_back(std::stof(ele14)); + feature.push_back(std::stof(ele15)); + feature.push_back(std::stof(ele16)); + feature.push_back(std::stof(ele17)); + feature.push_back(std::stof(ele18)); + feature.push_back(std::stof(ele19)); + feature.push_back(std::stof(ele20)); + feature.push_back(std::stof(ele21)); + feature.push_back(std::stof(ele22)); + feature.push_back(std::stof(ele23)); + feature.push_back(std::stof(ele24)); + + pocket_feature_dic[idx] = feature; +// for(int i =0; i< 24; i++){std::cout << i << feature[i] << " ";} +// std::cout << std::endl; +// std::cout << "feature size: " << feature.size() <> ele1 >> ele) + { +// std::cout << ele1 << " " << ele << std::endl; + atom_idx = std::stoi(ele); +// std::cout << atom_idx <> ele1 >> ele) + { + atom_idx = std::stoi(ele); + auto feature = pocket_feature_dic[atom_idx]; + atom_x = feature[0]; + atom_y = feature[1]; + atom_z = feature[2]; + vdwrad = feature[17]; + +// auto temp = (lig_num + idx) * 3; +// std::cout << temp < 1 || normals[i] < -1) { +// int t = i/3 * 3; +// std::cout << "normals " << t << " " << normals[t] < +#include +#include +#include + +#include "filenames.h" +#include "points.h" +#include "octree.h" +#include "cmd_flags.h" + +using namespace std; +using cflags::Require; +// +//DEFINE_string(filenames, kRequired, "", "The input filenames"); +//DEFINE_string(output_path, kOptional, ".", "The output path"); +//DEFINE_bool(verbose, kOptional, true, "Output logs"); +// +//void octree2pts(Points& point_cloud, const vector& label3, Octree& oct) { +// const int depth = oct.info().depth(); +// +// vector label0(label3), label1; +// for (int d = 3; d < 5; ++d) { +// int nnum = oct.info().node_num(d); +// const int* child = oct.children_cpu(d); +// +// label1.resize(oct.info().node_num(d + 1)); +// for (int i = 0; i < nnum; ++i) { +// int t = child[i]; +// if (t >= 0) { // non-empty +// for (int j = 0; j < 8; ++j) { +// label1[t * 8 + j] = label0[i]; +// } +// } +// } +// +// label0.swap(label1); +// } +// +// int num = oct.info().node_num(depth); +// const int* child_d = oct.children_cpu(depth); +// vector pts, normals, labels; +// for (int i = 0; i < num; ++i) { +// float n[3], pt[3]; +// oct.node_normal(n, i, depth); +// float len = abs(n[0]) + abs(n[1]) + abs(n[2]); +// if (len == 0) continue; +// oct.node_pos(pt, i, depth); +// +// for (int c = 0; c < 3; ++c) { +// normals.push_back(n[c]); +// pts.push_back(pt[c]); // !!! note the scale and bbmin +// } +// labels.push_back(label0[i]); +// } +// +// point_cloud.set_points(pts, normals, vector(), labels); +//} +// +//void load_labels(vector&label3, const string& filename) { +// label3.clear(); +// ifstream infile(filename); +// if (!infile) return; +// +// while (infile) { +// int i; +// infile >> i; +// label3.push_back(i); +// } +// infile.close(); +//} +// +//int main(int argc, char* argv[]) { +// bool succ = cflags::ParseCmd(argc, argv); +// if (!succ) { +// cflags::PrintHelpInfo("\nUsage: points2ply.exe"); +// return 0; +// } +// +// // file path +// string file_path = FLAGS_filenames; +// string output_path = FLAGS_output_path; +// if (output_path != ".") mkdir(output_path); +// else output_path = extract_path(file_path); +// output_path += "/"; +// +// vector all_files; +// get_all_filenames(all_files, file_path); +// +// for (int i = 0; i < all_files.size(); i++) { +// Octree oct; +// oct.read_octree(all_files[i]); +// +// string filename = all_files[i]; +// filename.replace(filename.find(".octree"), string::npos, ".txt"); +// vector label3; +// load_labels(label3, filename); +// +// Points pts; +// octree2pts(pts, label3, oct); +// +// filename = extract_filename(filename); +// if (FLAGS_verbose) cout << "Processing: " << filename << std::endl; +// filename = output_path + filename + ".points"; +// pts.write_points(filename); +// } +// +// return 0; +//} + + +////////////////////////////////////////// + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: points2ply.exe"); + return 0; + } + + // file path + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + + for (int i = 0; i < all_files.size(); i++) { + Octree oct; + oct.read_octree(all_files[i]); + + int depth = oct.info().depth(); + int num = oct.info().node_num(depth); + const float* feat = oct.feature_cpu(depth); + vector pts(3 * num, 0), buf(num, 0); + for (int i = 0; i < num; ++i) { + for (int j = 0; j < 3; ++j) { + pts[i * 3 + j] = feat[(3 + j)*num + i]; + } + } + + Points point_cloud; + point_cloud.set_points(pts, vector(), buf); + + string filename = extract_filename(all_files[i]); + if (FLAGS_verbose) cout << "Processing: " << filename << std::endl; + filename = output_path + filename + ".points"; + point_cloud.write_points(filename); + } + + return 0; +} diff --git a/octree/tools/ply2points.cpp b/octree/tools/ply2points.cpp new file mode 100644 index 0000000..d73b7c1 --- /dev/null +++ b/octree/tools/ply2points.cpp @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include + +#include "filenames.h" +#include "points.h" +#include "cmd_flags.h" + +using namespace std; +using cflags::Require; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_bool(const_normal, kOptional, "1", "Set constant normal if there is normal"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + +bool read_ply(vector& pts, vector& normals, vector& labels, + const string filename) { + ifstream infile(filename, ios::binary); + if (infile.fail()) { + cout << "Can not open " << filename << endl; + return false; + } + happly::PLYData plyIn(infile); + + // get vertex + pts = plyIn.getVertices(); + + // get normal + bool has_normal = plyIn.getElement("vertex").hasProperty("nx"); + if (has_normal) { + normals = plyIn.getNormals(); + } else { + normals.clear(); + } + + // get label + bool has_label = plyIn.getElement("vertex").hasProperty("label"); + if (has_label) { + labels = plyIn.getElement("vertex").getProperty("label"); + } + return true; +} + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: ply2points"); + return 0; + } + + // file path + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + + for (int i = 0; i < all_files.size(); i++) { + vector vtx, normal, label; + read_ply(vtx, normal, label, all_files[i]); + + string filename = extract_filename(all_files[i]); + if (FLAGS_verbose) cout << "Processing: " << filename << std::endl; + filename = output_path + filename + ".points"; + + Points pts; + bool succ = false; + if (normal.empty()) { + if (FLAGS_const_normal) { + normal.assign(vtx.size(), 0.57735f); + succ = pts.set_points(vtx, normal, vector{}, label); + } else { + vector feature(vtx.size() / 3, 1.0f); + succ = pts.set_points(vtx, normal, feature, label); + } + } else { + succ = pts.set_points(vtx, normal, vector{}, label); + } + + if (!succ) { + if (FLAGS_verbose) cout << "Failed: " << filename << std::endl; + continue; + } + pts.write_points(filename); + } + + return 0; +} diff --git a/octree/tools/points2ply.cpp b/octree/tools/points2ply.cpp new file mode 100644 index 0000000..8941bba --- /dev/null +++ b/octree/tools/points2ply.cpp @@ -0,0 +1,45 @@ +#include +#include +#include + +#include "filenames.h" +#include "points.h" +#include "cmd_flags.h" + +using namespace std; +using cflags::Require; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: points2ply.exe"); + return 0; + } + + // file path + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + + for (int i = 0; i < all_files.size(); i++) { + Points pts; + pts.read_points(all_files[i]); + + string filename = extract_filename(all_files[i]); + if (FLAGS_verbose) cout << "Processing: " << filename << std::endl; + filename = output_path + filename + ".ply"; + pts.write_ply(filename); + } + + return 0; +} diff --git a/octree/tools/points_noise.cpp b/octree/tools/points_noise.cpp new file mode 100644 index 0000000..9ded8af --- /dev/null +++ b/octree/tools/points_noise.cpp @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include + +#include "cmd_flags.h" +#include "points.h" +#include "filenames.h" + + +using std::vector; +using std::string; +using std::cout; +using std::endl; +using cflags::Require; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + +class GaussianRand { + public: + GaussianRand(float dev) + : generator_(static_cast(time(nullptr))), + distribution_(0, dev) {} + + void operator()(float& rnd) { + rnd = distribution_(generator_); + } + + void operator()(float* rnd, int n) { + for (int i = 0; i < n; ++i) { + rnd[i] = distribution_(generator_); + } + } + + private: + std::default_random_engine generator_; + std::normal_distribution distribution_; +}; + +class BernoulliRand { + public: + BernoulliRand(float p) + : generator_(static_cast(time(nullptr))), + distribution_(p) {} + + void operator()(int rnd) { + rnd = static_cast(distribution_(generator_)); + } + + void operator()(int* rnd, int n) { + for (int i = 0; i < n; ++i) { + rnd[i] = static_cast(distribution_(generator_)); + } + } + + private: + std::default_random_engine generator_; + std::bernoulli_distribution distribution_; +}; + + + +void add_noise(Points& pts) { + BernoulliRand bernoulli(0.1f); + GaussianRand pt_noise(3.0f), normal_noise(0.1f); + + // add pt noise + int npts = pts.info().pt_num(); + vector noise(npts); + pt_noise(noise.data(), npts); + vector mask(npts); + bernoulli(mask.data(), npts); + + float* ptr_pts = pts.mutable_ptr(PointsInfo::kPoint); + float* ptr_normal = pts.mutable_ptr(PointsInfo::kNormal); + for (int i = 0; i < npts; ++i) { + if (mask[i] == 0) continue; + for (int c = 0; c < 3; ++c) { + ptr_pts[i * 3 + c] += noise[i] * ptr_normal[i * 3 + c]; + } + } + + // add normal noise + noise.resize(3 * npts); + normal_noise(noise.data(), npts * 3); + for (int i = 0; i < npts; ++i) { + int ix3 = i * 3; + float len = 0; + for (int c = 0; c < 3; ++c) { + ptr_normal[ix3 + c] += noise[ix3 + c]; + len += ptr_normal[ix3 + c] * ptr_normal[ix3 + c]; + } + + len = sqrtf(len + 1.0e-10f); + for (int c = 0; c < 3; ++c) { + ptr_normal[ix3 + c] /= len; + } + } +} + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: simplify_points"); + return 0; + } + + // file path + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + + for (int i = 0; i < all_files.size(); i++) { + Points pts; + pts.read_points(all_files[i]); + + add_noise(pts); + + string filename = extract_filename(all_files[i]); + if (FLAGS_verbose) cout << "Processing: " << filename << std::endl; + filename = output_path + filename + "_noise.points"; + pts.write_points(filename); + } + + cout << "Done: " << FLAGS_filenames << endl; + return 0; +} \ No newline at end of file diff --git a/octree/tools/qq_octree_rotate.cpp b/octree/tools/qq_octree_rotate.cpp new file mode 100644 index 0000000..6868d30 --- /dev/null +++ b/octree/tools/qq_octree_rotate.cpp @@ -0,0 +1,213 @@ +// QQ add for xyz rotation + +#include +#include +#include +#include + +#include "cmd_flags.h" +#include "octree.h" +#include "filenames.h" +#include "math_functions.h" + +using std::vector; +using std::string; +using std::cout; +using std::endl; +using cflags::Require; +const float kPI = 3.14159265f; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_string(axis, kOptional, "y", "The upright axis of the input model"); +DEFINE_int(depth, kOptional, 6, "The maximum depth of the octree"); +DEFINE_int(full_depth, kOptional, 2, "The full layer of the octree"); +DEFINE_int(rot_num, kOptional, 24, "Number of poses rotated along the upright axis"); +DEFINE_float(offset, kOptional, 0.0f, "The offset value for handing thin shapes"); +DEFINE_bool(node_dis, kOptional, false, "Output per-node displacement"); +DEFINE_bool(node_feature, kOptional, false, "Compute per node feature"); +DEFINE_bool(split_label, kOptional, false, "Compute per node splitting label"); +DEFINE_bool(adaptive, kOptional, false, "Build adaptive octree"); +DEFINE_int(adp_depth, kOptional, 4, "The starting depth of adaptive octree"); +DEFINE_float(th_distance, kOptional, 2.0f, "The threshold for simplifying octree"); +DEFINE_float(th_normal, kOptional, 0.1f, "The threshold for simplifying octree"); +DEFINE_bool(key2xyz, kOptional, false, "Convert the key to xyz when serialization"); +DEFINE_bool(extrapolate, kOptional, false, "Exptrpolate the node feature"); +DEFINE_bool(save_pts, kOptional, false, "Save the average points as signal"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + + +// OctreeBuilder shows a basic example for building an octree with a point cloud +class OctreeBuilder { + public: + bool set_point_cloud(string filename) { + // load point cloud + bool succ = point_cloud_.read_points(filename); + if (!succ) { + cout << "Can not load " << filename << endl; + return false; + } + string msg; + succ = point_cloud_.info().check_format(msg); + if (!succ) { + cout << filename << endl << msg << endl; + return false; + } + + // deal with empty points + int npt = point_cloud_.info().pt_num(); + if (npt == 0) { + cout << "This is an empty points!" << endl; + return false; + } + cout << "Points number: " << npt < 1.0e-10f) { + float offset = FLAGS_offset * 2.0f * radius_ / float(1 << FLAGS_depth); + point_cloud_.displace(offset); + radius_ += offset; + } + + return true; + } + + void set_octree_info() { + octree_info_.initialize(FLAGS_depth, FLAGS_full_depth, FLAGS_node_dis, + FLAGS_node_feature, FLAGS_split_label, FLAGS_adaptive, FLAGS_adp_depth, + FLAGS_th_distance, FLAGS_th_normal, FLAGS_key2xyz, FLAGS_extrapolate, + FLAGS_save_pts, point_cloud_); + + // the point cloud has been centralized, + // so initializing the bbmin & bbmax in the following way + float bbmin[] = { -radius_, -radius_, -radius_ }; + float bbmax[] = { radius_, radius_, radius_ }; + octree_info_.set_bbox(bbmin, bbmax); +// std::cout << octree_info_.depth() << std::endl; + } + + void build_octree() { + octree_.build(octree_info_, point_cloud_); +// std::cout << point_cloud_.info + } + + void save_octree(const string& output_filename) { + // Modify the bounding box before saving, because the center of + // the point cloud is translated to (0, 0, 0) when building the octree + octree_.mutable_info().set_bbox(radius_, center_); + octree_.write_octree(output_filename); + } + + public: + Points point_cloud_; + float radius_, center_[3]; + OctreeInfo octree_info_; + Octree octree_; +}; + + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: Octree.exe"); + return 0; + } + + // file path + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + + #pragma omp parallel for + for (int i = 0; i < all_files.size(); i++) { + OctreeBuilder builder; + bool succ = builder.set_point_cloud(all_files[i]); + + string filename = extract_filename(all_files[i]); + if (!succ) { + if (FLAGS_verbose) cout << "Warning: " + filename + " is invalid!\n"; + continue; + } + builder.set_octree_info(); + + // data augmentation +// float angle = 2.0f * kPI / float(FLAGS_rot_num); + float deg_90 = kPI * 1.0f/2; + float deg_180 = kPI; +// float axis[] = { 0.0f, 0.0f, 0.0f }; +// if (FLAGS_axis == "x") axis[0] = 1.0f; +// else if (FLAGS_axis == "y") axis[1] = 1.0f; +// else axis[2] = 1.0f; + + float x_axis[] = { 1.0f, 0.0f, 0.0f }; + float y_axis[] = { 0.0f, 1.0f, 0.0f }; + float z_axis[] = { 0.0f, 0.0f, 1.0f }; + + + if (FLAGS_verbose) cout << "Processing: " + filename + "\n"; + // QQ: get 24 augmentations + int idx = 0; + for (int v = 0; v < 4; ++v){ + builder.point_cloud_.rotate(deg_90, x_axis); + + for (int t= 0; t < 4; ++t){ + builder.point_cloud_.rotate(deg_90, z_axis); + + // output filename + char file_suffix[64]; + sprintf(file_suffix, "_%d_%d_%03d.octree", FLAGS_depth, FLAGS_full_depth, idx); + idx++; + + // build + builder.build_octree(); + builder.save_octree(output_path + filename + file_suffix); + } + } + + builder.point_cloud_.rotate(deg_90, y_axis); + + for (int t= 0; t < 4; ++t){ + builder.point_cloud_.rotate(deg_90, z_axis); + + // output filename + char file_suffix[64]; + sprintf(file_suffix, "_%d_%d_%03d.octree", FLAGS_depth, FLAGS_full_depth, idx); + idx++; + + // build + builder.build_octree(); + builder.save_octree(output_path + filename + file_suffix); + } + + builder.point_cloud_.rotate(deg_180, y_axis); + + for (int t= 0; t < 4; ++t){ + builder.point_cloud_.rotate(deg_90, z_axis); + + // output filename + char file_suffix[64]; + sprintf(file_suffix, "_%d_%d_%03d.octree", FLAGS_depth, FLAGS_full_depth, idx); + idx++; + + // build + builder.build_octree(); + builder.save_octree(output_path + filename + file_suffix); + } + } + + if (FLAGS_verbose) cout << "Done: " << FLAGS_filenames << endl; + return 0; +} + diff --git a/octree/tools/qq_octree_rotate_angle.cpp b/octree/tools/qq_octree_rotate_angle.cpp new file mode 100644 index 0000000..d8b10a7 --- /dev/null +++ b/octree/tools/qq_octree_rotate_angle.cpp @@ -0,0 +1,187 @@ +// Not working when multiple values in angle are non-zero. + +#include +#include +#include +#include + +#include "cmd_flags.h" +#include "octree.h" +#include "filenames.h" +#include "math_functions.h" + +using std::vector; +using std::string; +using std::cout; +using std::endl; +using cflags::Require; +const float kPI = 3.14159265f; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_string(axis, kOptional, "y", "The upright axis of the input model"); +DEFINE_int(depth, kOptional, 6, "The maximum depth of the octree"); +DEFINE_int(full_depth, kOptional, 2, "The full layer of the octree"); +DEFINE_int(rot_num, kOptional, 12, "Number of poses rotated along the upright axis"); +DEFINE_float(offset, kOptional, 0.55f, "The offset value for handing thin shapes"); +DEFINE_bool(node_dis, kOptional, false, "Output per-node displacement"); +DEFINE_bool(node_feature, kOptional, false, "Compute per node feature"); +DEFINE_bool(split_label, kOptional, false, "Compute per node splitting label"); +DEFINE_bool(adaptive, kOptional, false, "Build adaptive octree"); +DEFINE_int(adp_depth, kOptional, 4, "The starting depth of adaptive octree"); +DEFINE_float(th_distance, kOptional, 2.0f, "The threshold for simplifying octree"); +DEFINE_float(th_normal, kOptional, 0.1f, "The threshold for simplifying octree"); +DEFINE_bool(key2xyz, kOptional, false, "Convert the key to xyz when serialization"); +DEFINE_bool(extrapolate, kOptional, false, "Exptrpolate the node feature"); +DEFINE_bool(save_pts, kOptional, false, "Save the average points as signal"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + + +// OctreeBuilder shows a basic example for building an octree with a point cloud +class OctreeBuilder { + public: + bool set_point_cloud(string filename) { + // load point cloud + bool succ = point_cloud_.read_points(filename); + if (!succ) { + cout << "Can not load " << filename << endl; + return false; + } + string msg; + succ = point_cloud_.info().check_format(msg); + if (!succ) { + cout << filename << endl << msg << endl; + return false; + } + + // deal with empty points + int npt = point_cloud_.info().pt_num(); + if (npt == 0) { + cout << "This is an empty points!" << endl; + return false; + } + cout << "Points number: " << npt < 1.0e-10f) { + float offset = FLAGS_offset * 2.0f * radius_ / float(1 << FLAGS_depth); + point_cloud_.displace(offset); + radius_ += offset; + } + + return true; + } + + void set_octree_info() { + octree_info_.initialize(FLAGS_depth, FLAGS_full_depth, FLAGS_node_dis, + FLAGS_node_feature, FLAGS_split_label, FLAGS_adaptive, FLAGS_adp_depth, + FLAGS_th_distance, FLAGS_th_normal, FLAGS_key2xyz, FLAGS_extrapolate, + FLAGS_save_pts, point_cloud_); + + // the point cloud has been centralized, + // so initializing the bbmin & bbmax in the following way + float bbmin[] = { -radius_, -radius_, -radius_ }; + float bbmax[] = { radius_, radius_, radius_ }; + octree_info_.set_bbox(bbmin, bbmax); +// std::cout << octree_info_.depth() << std::endl; + } + + void build_octree() { + octree_.build(octree_info_, point_cloud_); +// std::cout << point_cloud_.info + } + + void save_octree(const string& output_filename) { + // Modify the bounding box before saving, because the center of + // the point cloud is translated to (0, 0, 0) when building the octree + octree_.mutable_info().set_bbox(radius_, center_); + octree_.write_octree(output_filename); + } + + public: + Points point_cloud_; + float radius_, center_[3]; + OctreeInfo octree_info_; + Octree octree_; +}; + + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: Octree.exe"); + return 0; + } + + // file path + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + + #pragma omp parallel for + for (int i = 0; i < all_files.size(); i++) { + OctreeBuilder builder; + bool succ = builder.set_point_cloud(all_files[i]); + + string filename = extract_filename(all_files[i]); + if (!succ) { + if (FLAGS_verbose) cout << "Warning: " + filename + " is invalid!\n"; + continue; + } + builder.set_octree_info(); + + // data augmentation +// float angle = 2.0f * kPI / float(FLAGS_rot_num); + float angle[3] = { 0 }; + angle[0] = 1; + angle[1] = 1; + angle[2] = 1; + +// float axis[] = { 0.0f, 0.0f, 0.0f }; +// if (FLAGS_axis == "x") axis[0] = 1.0f; +// else if (FLAGS_axis == "y") axis[1] = 1.0f; +// else axis[2] = 1.0f; + + if (FLAGS_verbose) cout << "Processing: " + filename + "\n"; + for (int v = 0; v < FLAGS_rot_num; ++v) { + // output filename + char file_suffix[64]; + sprintf(file_suffix, "_%d_%d_%03d.octree", FLAGS_depth, FLAGS_full_depth, v); + + + // build + builder.build_octree(); + + // save octree + builder.save_octree(output_path + filename + file_suffix); + +// char file_suffix2[64]; +// sprintf(file_suffix2, "_%d_%d_%03d.ply", FLAGS_depth, FLAGS_full_depth, v); +// builder.point_cloud_.write_ply(output_path + filename + file_suffix2); + + // rotate point for the next iteration + builder.point_cloud_.rotate(angle); + + + + // message + //cout << "Processing: " << filename.substr(filename.rfind('\\') + 1) << endl; + } + } + + if (FLAGS_verbose) cout << "Done: " << FLAGS_filenames << endl; + return 0; +} + diff --git a/octree/tools/simplify_points.cpp b/octree/tools/simplify_points.cpp new file mode 100644 index 0000000..279ca99 --- /dev/null +++ b/octree/tools/simplify_points.cpp @@ -0,0 +1,59 @@ +#include +#include + +#include "cmd_flags.h" +#include "filenames.h" +#include "simplify_points.h" + +using std::string; +using cflags::Require; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_int(dim, kOptional, 256, "The maximum resolution"); +DEFINE_float(offset, kOptional, 0.55f, "The offset value for handing thin shapes"); +DEFINE_bool(avg_points, kOptional, true, "Average points as output"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + + +// The points must have normals! Can not deal with labela and feature! (TODO) + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: simplify_points"); + return 0; + } + + // file path + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; +// get_all_filenames(all_files, file_path); + get_all_filenames_qq(all_files, file_path); + + //#pragma omp parallel for + SimplifyPoints simplify_pts(FLAGS_dim, FLAGS_avg_points, FLAGS_offset); + for (int i = 0; i < all_files.size(); i++) { + string error_msg = simplify_pts.set_point_cloud(output_path+all_files[i]); //qq revised: was set_point_cloud(all_files[i]) + if (!error_msg.empty()) { + std::cout << error_msg << std::endl; + continue; + } + + simplify_pts.simplify(); + string path = extract_path(output_path+all_files[i]); // qq add + string filename = extract_filename(all_files[i]); + if (FLAGS_verbose) { + std::cout << "Processing: " + filename + "\n"; + } + simplify_pts.write_point_cloud(path +"/" +filename + ".smp.points"); + } + + std::cout << "Done: " << FLAGS_filenames << std::endl; + return 0; +} \ No newline at end of file diff --git a/octree/tools/transform_points.cpp b/octree/tools/transform_points.cpp new file mode 100644 index 0000000..47c5553 --- /dev/null +++ b/octree/tools/transform_points.cpp @@ -0,0 +1,97 @@ +#include +#include + +#include "points.h" +#include "filenames.h" +#include "cmd_flags.h" +#include "math_functions.h" +#include "transform_points.h" + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_float(scale, kOptional, 1.0, "The scale factor"); +DEFINE_float(trans, kOptional, 0.0, "The translation factor"); +DEFINE_float(offset, kOptional, 0.0, "Offset the points along its normal"); +DEFINE_float(ratio, kOptional, 0.5, "The dropout ratio"); +DEFINE_float(dim, kOptional, 0, "The resolution for dropout"); +DEFINE_float(std_nm, kOptional, 0.0, "The std of normal noise "); +DEFINE_float(std_pt, kOptional, 0.0, "The std of posistion noise"); +DEFINE_string(mat, kOptional, "", "A txt file which contains a matrix"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + +using std::cout; +using std::vector; + +vector load_matrix(const string& filename) { + vector mat; + std::ifstream infile(filename); + if (!infile) { return mat; } + while (infile) { + float m = 0; + infile >> m; + mat.push_back(m); + } + return mat; +} + + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: transform_points"); + return 0; + } + + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + for (int i = 0; i < all_files.size(); i++) { + Points pts; + pts.read_points(all_files[i]); + + if (FLAGS_trans != 0.0) { + float trans[3] = { FLAGS_trans, FLAGS_trans, FLAGS_trans }; + pts.translate(trans); + } + + if (FLAGS_scale != 1.0) { + pts.uniform_scale(FLAGS_scale); + } + + if (FLAGS_offset != 0) { + pts.displace(FLAGS_offset); + } + + if (FLAGS_mat.empty() == false) { + vector mat = load_matrix(FLAGS_mat); + if (mat.size() > 9) { + pts.transform(mat.data()); + } + } + + if (FLAGS_dim > 0) { + float radius, center[3]; + bounding_sphere(radius, center, pts.points(), pts.info().pt_num()); + float bbmin[3] = { center[0] - radius, center[1] - radius, center[2] - radius }; + float bbmax[3] = { center[0] + radius, center[1] + radius, center[2] + radius }; + DropPoints drop_points(FLAGS_dim, FLAGS_ratio, bbmin, bbmax); + drop_points.dropout(pts); + } + + if (FLAGS_std_pt > 1.0e-5f || FLAGS_std_nm > 1.0e-5f) { + pts.add_noise(FLAGS_std_pt, FLAGS_std_nm); + } + + // get filename + string filename = extract_filename(all_files[i]); + if (FLAGS_verbose) cout << "Processing: " << filename << std::endl; + pts.write_points(output_path + filename + ".trans.points"); + } + + return 0; +} \ No newline at end of file diff --git a/octree/tools/upgrade_octree.cpp b/octree/tools/upgrade_octree.cpp new file mode 100644 index 0000000..5456a94 --- /dev/null +++ b/octree/tools/upgrade_octree.cpp @@ -0,0 +1,166 @@ +#include +#include +#include +#include +#include + +#include "cmd_flags.h" +#include "octree.h" +#include "filenames.h" + +using std::vector; +using std::string; +using std::cout; +using cflags::Require; +const float kPI = 3.14159265f; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_bool(node_dis, kOptional, false, "Output per-node displacement"); +DEFINE_bool(node_label, kOptional, false, "Whether has node label"); +DEFINE_bool(split_label, kOptional, false, "Compute per node splitting label"); +DEFINE_bool(adaptive, kOptional, false, "Build adaptive octree"); +DEFINE_int(adp_depth, kOptional, 4, "The starting depth of adaptive octree"); +DEFINE_bool(key2xyz, kOptional, true, "Convert the key to xyz when serialization"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + + +bool load_raw_data(vector& data, const string& filename) { + std::ifstream infile(filename, std::ios::binary); + if (!infile) return false; + + infile.seekg(0, infile.end); + int len = infile.tellg(); + infile.seekg(0, infile.beg); + + data.resize(len); + infile.read(data.data(), len); + return true; +} + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: update_octree.exe"); + return 0; + } + if (FLAGS_node_label && FLAGS_split_label) { + cout << "The node_label and split_label can not be true at the same time\n"; + return 0; + } + + // file path + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + + for (int i = 0; i < all_files.size(); i++) { + vector oct_input; + bool succ = load_raw_data(oct_input, all_files[i]); + if (!succ) continue; + + /// parse the octree + int* octi = (int*)oct_input.data(); + int total_node_num = octi[0]; + int final_node_num = octi[1]; + int depth = octi[2]; + int full_layer = octi[3]; + const int* node_num = octi + 4; + const int* node_num_cum = node_num + depth + 1; + const int* key = node_num_cum + depth + 2; + const int* children = key + total_node_num; + const int* data = children + total_node_num; + const float* normal_ptr = (const float*)data; + + vector nnum_nempty(depth + 1, 0); + for (int d = 0; d <= depth; ++d) { + // find the last element which is not equal to -1 + const int* children_d = children + node_num_cum[d]; + for (int i = node_num[d] - 1; i >= 0; i--) { + if (children_d[i] != -1) { + nnum_nempty[d] = children_d[i] + 1; + break; + } + } + } + + /// set octree info + OctreeInfo octree_info; + octree_info.set_batch_size(1); + octree_info.set_depth(depth); + octree_info.set_full_layer(full_layer); + octree_info.set_adaptive_layer(FLAGS_adp_depth); + octree_info.set_adaptive(FLAGS_adaptive); + octree_info.set_node_dis(FLAGS_node_dis); + octree_info.set_key2xyz(FLAGS_key2xyz); + octree_info.set_threshold_normal(0.0f); + octree_info.set_threshold_dist(0.0f); + + float width = static_cast(1 << depth); + float bbmin[] = {0.0f, 0.0f, 0.0f }; + float bbmax[] = {width, width, width }; + octree_info.set_bbox(bbmin, bbmax); + + // by default, the octree contains Key and Child + int channel = 1; + octree_info.set_property(OctreeInfo::kKey, channel, -1); + octree_info.set_property(OctreeInfo::kChild, channel, -1); + + // set feature + int data_channel = FLAGS_node_dis ? 4 : 3; + int location = FLAGS_adaptive ? -1 : depth; + octree_info.set_property(OctreeInfo::kFeature, data_channel, location); + + // set label + if (FLAGS_node_label) { + octree_info.set_property(OctreeInfo::kLabel, 1, depth); + } + + // set split label + octree_info.set_property(OctreeInfo::kSplit, 0, 0); + if (FLAGS_split_label) { + octree_info.set_property(OctreeInfo::kSplit, 1, -1); + } + + octree_info.set_nnum(node_num); + octree_info.set_nnum_cum(); + octree_info.set_nempty(nnum_nempty.data()); + octree_info.set_ptr_dis(); + + /// output octree + vector buffer(octree_info.sizeof_octree()); + memcpy(buffer.data(), &octree_info, sizeof(OctreeInfo)); + Octree oct_parser; + oct_parser.set_octree(buffer); + int total_nnum = octree_info.total_nnum(); + int nnum_depth = octree_info.node_num(depth); + memcpy(oct_parser.mutable_key_cpu(0), key, total_nnum * sizeof(int)); + memcpy(oct_parser.mutable_children_cpu(0), children, total_nnum * sizeof(int)); + int data_size = FLAGS_adaptive ? total_nnum : nnum_depth; + memcpy(oct_parser.mutable_feature_cpu(0), data, data_size * data_channel * sizeof(float)); + if (FLAGS_node_label) { + const int* ptr = data + data_size * data_channel; + float* des = oct_parser.mutable_label_cpu(0); + for (int i = 0; i < nnum_depth; ++i) { + des[i] = static_cast(ptr[i]); + } + } + if (FLAGS_split_label) { + const int* ptr = data + data_size * data_channel; + float* des = oct_parser.mutable_split_cpu(0); + for (int i = 0; i < total_nnum; ++i) { + des[i] = static_cast(ptr[i]); + } + } + string filename = extract_filename(all_files[i]); + oct_parser.write_octree(output_path + filename + ".upgrade.octree"); + } + + return 0; +} + diff --git a/octree/tools/upgrade_points.cpp b/octree/tools/upgrade_points.cpp new file mode 100644 index 0000000..d8641d2 --- /dev/null +++ b/octree/tools/upgrade_points.cpp @@ -0,0 +1,111 @@ +#include +#include +#include +#include + +#include "filenames.h" +#include "points.h" +#include "cmd_flags.h" + +using namespace std; + +DEFINE_string(filenames, kRequired, "", "The input filenames"); +DEFINE_string(output_path, kOptional, ".", "The output path"); +DEFINE_bool(has_label, kOptional, false, "The file contains label"); +DEFINE_bool(verbose, kOptional, true, "Output logs"); + + +void load_pointcloud(vector& pt, vector& normal, + vector& seg, const string& filename) { + const int channel = 3; + std::ifstream infile(filename, std::ios::binary); + + infile.seekg(0, infile.end); + size_t len = infile.tellg(); + infile.seekg(0, infile.beg); + + int n; + infile.read((char*)(&n), sizeof(int)); + pt.resize(3 * n); + infile.read((char*)pt.data(), sizeof(float) * 3 * n); + normal.resize(channel * n); + infile.read((char*)normal.data(), sizeof(float) * channel * n); + + if (FLAGS_has_label && + 6 * n * sizeof(float) + (n + 1) * sizeof(int) == len) { + seg.resize(n); + infile.read((char*)seg.data(), sizeof(int)*n); + } + + infile.close(); +} + +void gen_test_pointcloud(vector& pt, vector& normal, + vector& label) { + const float pt_input[] = { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f}; + const float nm_input[] = { 1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f }; + const float lb_input[] = { 0.0f, 1.0f }; + + pt.assign(pt_input, pt_input + 6); + normal.assign(nm_input, nm_input + 6); + label.assign(lb_input, lb_input + 2); +} + +int main(int argc, char* argv[]) { + bool succ = cflags::ParseCmd(argc, argv); + if (!succ) { + cflags::PrintHelpInfo("\nUsage: upgrade_points.exe"); + return 0; + } + + // file path + string file_path = FLAGS_filenames; + string output_path = FLAGS_output_path; + if (output_path != ".") mkdir(output_path); + else output_path = extract_path(file_path); + output_path += "/"; + + vector all_files; + get_all_filenames(all_files, file_path); + //#pragma omp parallel for + for (int i = 0; i < all_files.size(); ++i) { + string filename = extract_filename(all_files[i]); + + /// from the old formate to this new format + vector pts, normals, labels; + vector seg; + load_pointcloud(pts, normals, seg, all_files[i]); + if (FLAGS_has_label && seg.size() == 0) { + cout << "Error in " << filename << endl; + continue; + } + labels.assign(seg.begin(), seg.end()); + + Points points; + points.set_points(pts, normals, vector(), labels); + + points.write_points(output_path + filename + ".upgrade.points"); + + if (FLAGS_verbose) cout << "Processing: " + filename + "\n"; + } + cout << "Done: " << FLAGS_filenames << endl; + //// generate testing points + //vector pts, normals, labels; + //gen_test_pointcloud(pts, normals, labels); + //Points points; + //points.set_points(pts, normals, vector(), labels); + //points.write_points("test.points"); + + ///// backward conversion: from this new formate to the old format + //points.load_points(filename + ".points"); + //ofstream outfile(filename + "_1.points", ofstream::binary); + //int n = points.info().pt_num(); + //outfile.write((char*)(&n), sizeof(int)); + //outfile.write((const char*)points.prop_ptr(PtsInfo::kPoint), + // n * sizeof(float) * points.info().channel(PtsInfo::kPoint)); + //outfile.write((const char*)points.prop_ptr(PtsInfo::kNormal), + // n * sizeof(float) * points.info().channel(PtsInfo::kNormal)); + //outfile.close(); + + return 0; +} diff --git a/pdbbind/.DS_Store b/pdbbind/.DS_Store new file mode 100644 index 0000000..d381763 Binary files /dev/null and b/pdbbind/.DS_Store differ diff --git a/pdbbind/Elements.java b/pdbbind/Elements.java new file mode 100755 index 0000000..070bb3a --- /dev/null +++ b/pdbbind/Elements.java @@ -0,0 +1,509 @@ +/* + * Copyright (C) 2006-2012 Egon Willighagen + * 2014 Mark B Vine (orcid:0000-0002-7794-0426) + * + * Contact: cdk-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * All we ask is that proper credit is given for our work, which includes + * - but is not limited to - adding the above copyright notice to the beginning + * of your source code files, and to any copyright notice that you may distribute + * with programs based on this work. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +package org.openscience.cdk.config; + +import org.openscience.cdk.interfaces.IElement; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * Enumeration of chemical elements. Data is taken from the Blue Obelisk Data + * Repository, version 3. This enumeration is auto-generated with utilities + * found in the 'cdk-build-utils' project. + * + * @author egonw + * @author john may + * @cdk.module core + * @cdk.githash + */ +public enum Elements { + Unknown(0, "", 0, 0, null, 0.00, null), + Hydrogen(1, "H", 1, 1, 0.37, 1.20, 2.20), + Helium(2, "He", 1, 18, 0.32, 1.40, null), + Lithium(3, "Li", 2, 1, 1.34, 2.20, 0.98), + Beryllium(4, "Be", 2, 2, 0.90, 1.90, 1.57), + Boron(5, "B", 2, 13, 0.82, 1.80, 2.04), + Carbon(6, "C", 2, 14, 0.77, 1.70, 2.55), + Nitrogen(7, "N", 2, 15, 0.75, 1.60, 3.04), + Oxygen(8, "O", 2, 16, 0.73, 1.55, 3.44), + Fluorine(9, "F", 2, 17, 0.71, 1.50, 3.98), + Neon(10, "Ne", 2, 18, 0.69, 1.54, null), + Sodium(11, "Na", 3, 1, 1.54, 2.40, 0.93), + Magnesium(12, "Mg", 3, 2, 1.30, 2.20, 1.31), + Aluminium(13, "Al", 3, 13, 1.18, 2.10, 1.61), + Silicon(14, "Si", 3, 14, 1.11, 2.10, 1.90), + Phosphorus(15, "P", 3, 15, 1.06, 1.95, 2.19), + Sulfur(16, "S", 3, 16, 1.02, 1.80, 2.58), + Chlorine(17, "Cl", 3, 17, 0.99, 1.80, 3.16), + Argon(18, "Ar", 3, 18, 0.97, 1.88, null), + Potassium(19, "K", 4, 1, 1.96, 2.80, 0.82), + Calcium(20, "Ca", 4, 2, 1.74, 2.40, 1.00), + Scandium(21, "Sc", 4, 3, 1.44, 2.30, 1.36), + Titanium(22, "Ti", 4, 4, 1.36, 2.15, 1.54), + Vanadium(23, "V", 4, 5, 1.25, 2.05, 1.63), + Chromium(24, "Cr", 4, 6, 1.27, 2.05, 1.66), + Manganese(25, "Mn", 4, 7, 1.39, 2.05, 1.55), + Iron(26, "Fe", 4, 8, 1.25, 2.05, 1.83), + Cobalt(27, "Co", 4, 9, 1.26, 2.0, 1.88), + Nickel(28, "Ni", 4, 10, 1.21, 2.0, 1.91), + Copper(29, "Cu", 4, 11, 1.38, 2.0, 1.90), + Zinc(30, "Zn", 4, 12, 1.31, 2.10, 1.65), + Gallium(31, "Ga", 4, 13, 1.26, 2.10, 1.81), + Germanium(32, "Ge", 4, 14, 1.22, 2.10, 2.01), + Arsenic(33, "As", 4, 15, 1.19, 2.05, 2.18), + Selenium(34, "Se", 4, 16, 1.16, 1.90, 2.55), + Bromine(35, "Br", 4, 17, 1.14, 1.90, 2.96), + Krypton(36, "Kr", 4, 18, 1.10, 2.02, 3.00), + Rubidium(37, "Rb", 5, 1, 2.11, 2.90, 0.82), + Strontium(38, "Sr", 5, 2, 1.92, 2.55, 0.95), + Yttrium(39, "Y", 5, 3, 1.62, 2.40, 1.22), + Zirconium(40, "Zr", 5, 4, 1.48, 2.30, 1.33), + Niobium(41, "Nb", 5, 5, 1.37, 2.15, 1.60), + Molybdenum(42, "Mo", 5, 6, 1.45, 2.10, 2.16), + Technetium(43, "Tc", 5, 7, 1.56, 2.05, 1.90), + Ruthenium(44, "Ru", 5, 8, 1.26, 2.05, 2.20), + Rhodium(45, "Rh", 5, 9, 1.35, 2.0, 2.28), + Palladium(46, "Pd", 5, 10, 1.31, 2.05, 2.20), + Silver(47, "Ag", 5, 11, 1.53, 2.10, 1.93), + Cadmium(48, "Cd", 5, 12, 1.48, 2.20, 1.69), + Indium(49, "In", 5, 13, 1.44, 2.20, 1.78), + Tin(50, "Sn", 5, 14, 1.41, 2.25, 1.96), + Antimony(51, "Sb", 5, 15, 1.38, 2.20, 2.05), + Tellurium(52, "Te", 5, 16, 1.35, 2.10, 2.10), + Iodine(53, "I", 5, 17, 1.33, 2.10, 2.66), + Xenon(54, "Xe", 5, 18, 1.30, 2.16, 2.60), + Caesium(55, "Cs", 6, 1, 2.25, 3.00, 0.79), + Barium(56, "Ba", 6, 2, 1.98, 2.70, 0.89), + Lanthanum(57, "La", 6, 3, 1.69, 2.50, 1.10), + Cerium(58, "Ce", 6, 0, null, 2.48, 1.12), + Praseodymium(59, "Pr", 6, 0, null, 2.47, 1.13), + Neodymium(60, "Nd", 6, 0, null, 2.45, 1.14), + Promethium(61, "Pm", 6, 0, null, 2.43, null), + Samarium(62, "Sm", 6, 0, null, 2.42, 1.17), + Europium(63, "Eu", 6, 0, 2.40, 2.40, null), + Gadolinium(64, "Gd", 6, 0, null, 2.38, 1.20), + Terbium(65, "Tb", 6, 0, null, 2.37, null), + Dysprosium(66, "Dy", 6, 0, null, 2.35, 1.22), + Holmium(67, "Ho", 6, 0, null, 2.33, 1.23), + Erbium(68, "Er", 6, 0, null, 2.32, 1.24), + Thulium(69, "Tm", 6, 0, null, 2.30, 1.25), + Ytterbium(70, "Yb", 6, 0, null, 2.28, null), + Lutetium(71, "Lu", 6, 0, 1.60, 2.27, 1.27), + Hafnium(72, "Hf", 6, 4, 1.50, 2.25, 1.30), + Tantalum(73, "Ta", 6, 5, 1.38, 2.20, 1.50), + Tungsten(74, "W", 6, 6, 1.46, 2.10, 2.36), + Rhenium(75, "Re", 6, 7, 1.59, 2.05, 1.90), + Osmium(76, "Os", 6, 8, 1.28, 2.0, 2.20), + Iridium(77, "Ir", 6, 9, 1.37, 2.0, 2.20), + Platinum(78, "Pt", 6, 10, 1.28, 2.05, 2.28), + Gold(79, "Au", 6, 11, 1.44, 2.10, 2.54), + Mercury(80, "Hg", 6, 12, 1.49, 2.05, 2.00), + Thallium(81, "Tl", 6, 13, 1.48, 2.20, 1.62), + Lead(82, "Pb", 6, 14, 1.47, 2.30, 2.33), + Bismuth(83, "Bi", 6, 15, 1.46, 2.30, 2.02), + Polonium(84, "Po", 6, 16, 1.46, null, 2.00), + Astatine(85, "At", 6, 17, null, null, 2.20), + Radon(86, "Rn", 6, 18, 1.45, null, null), + Francium(87, "Fr", 7, 1, null, null, 0.70), + Radium(88, "Ra", 7, 2, null, null, 0.90), + Actinium(89, "Ac", 7, 3, null, null, 1.10), + Thorium(90, "Th", 7, 0, null, 2.40, 1.30), + Protactinium(91, "Pa", 7, 0, null, null, 1.50), + Uranium(92, "U", 7, 0, null, 2.30, 1.38), + Neptunium(93, "Np", 7, 0, null, null, 1.36), + Plutonium(94, "Pu", 7, 0, null, null, 1.28), + Americium(95, "Am", 7, 0, null, null, 1.30), + Curium(96, "Cm", 7, 0, null, null, 1.30), + Berkelium(97, "Bk", 7, 0, null, null, 1.30), + Californium(98, "Cf", 7, 0, null, null, 1.30), + Einsteinium(99, "Es", 7, 0, null, null, 1.30), + Fermium(100, "Fm", 7, 0, null, null, 1.30), + Mendelevium(101, "Md", 7, 0, null, null, 1.30), + Nobelium(102, "No", 7, 0, null, null, 1.30), + Lawrencium(103, "Lr", 7, 0, null, null, null), + Rutherfordium(104, "Rf", 7, 4, null, null, null), + Dubnium(105, "Db", 7, 5, null, null, null), + Seaborgium(106, "Sg", 7, 6, null, null, null), + Bohrium(107, "Bh", 7, 7, null, null, null), + Hassium(108, "Hs", 7, 8, null, null, null), + Meitnerium(109, "Mt", 7, 9, null, null, null), + Darmstadtium(110, "Ds", 7, 10, null, null, null), + Roentgenium(111, "Rg", 7, 11, null, null, null), + Copernicium(112, "Cn", 7, 12, null, null, null), + @Deprecated + Ununtrium(113, "Uut", 7, 13, null, null, null), + Nihonium(113, "Nh", 7, 13, null, null, null), + Flerovium(114, "Fl", 7, 14, null, null, null), + @Deprecated + Ununpentium(115, "Uup", 7, 15, null, null, null), + Moscovium(115, "Mc", 7, 15, null, null, null), + Livermorium(116, "Lv", 7, 16, null, null, null), + @Deprecated + Ununseptium(117, "Uus", 7, 17, null, null, null), + Tennessine(117, "Ts", 7, 17, null, null, null), + @Deprecated + Ununoctium(118, "Uuo", 7, 18, null, null, null), + Oganesson(118, "Og", 7, 18, null, null, null); + + /** + * Atomic number, periodic table period and group. + */ + private final int number, period, group; + + /** + * The symbol of the element. + */ + private final String symbol; + + /** + * Covalent radius (rcov), van der Waals radius + * (rw) and Pauling electronegativity. + */ + private final Double rCov, rW, electronegativity; + + /** + * An {@link IElement} instance of this element. + */ + private final IElement instance; + + /** + * Lookup elements by atomic number. + */ + static final Elements[] NUMER_MAP = new Elements[119]; + + /** + * Lookup elements by symbol / name. + */ + static final Map SYMBOL_MAP = new HashMap(400); + + static { + // index elements + for (final Elements e : values()) { + NUMER_MAP[e.number] = e; + SYMBOL_MAP.put(e.symbol.toLowerCase(Locale.ENGLISH), e); + SYMBOL_MAP.put(e.name().toLowerCase(Locale.ENGLISH), e); + } + + // recently named elements + SYMBOL_MAP.put("uub", Copernicium); // 2009 + SYMBOL_MAP.put("ununbium", Copernicium); + + SYMBOL_MAP.put("uuq", Flerovium); // 2012 + SYMBOL_MAP.put("ununquadium", Flerovium); + + SYMBOL_MAP.put("uuh", Livermorium); // 2012 + SYMBOL_MAP.put("ununhexium", Livermorium); + + // 2016 + SYMBOL_MAP.put("uut", Nihonium); + SYMBOL_MAP.put("uup", Moscovium); + SYMBOL_MAP.put("uus", Tennessine); + SYMBOL_MAP.put("uuo", Oganesson); + + // alternative spellings + SYMBOL_MAP.put("sulphur", Sulfur); + SYMBOL_MAP.put("cesium", Caesium); + SYMBOL_MAP.put("aluminum", Aluminium); + + } + + /** + * Internal constructor. + * + * @param number atomic number + * @param symbol symbol + * @param period periodic table period + * @param group periodic table group + * @param rCov covalent radius + * @param rW van der Waals radius + * @param electronegativity pauling electronegativity + */ + private Elements(int number, String symbol, int period, int group, Double rCov, Double rW, Double electronegativity) { + this.number = number; + this.period = period; + this.group = group; + this.symbol = symbol; + this.rCov = rCov; + this.rW = rW; + this.electronegativity = electronegativity; + this.instance = new NaturalElement(symbol, number); + } + + /** + * The atomic number of the element. An {@link #Unknown} element + * has an atomic number of '0'. + * + * @return 0 - 116 + */ + public int number() { + return number; + } + + /** + * The element symbol, C for carbon, N for nitrogen, Na for sodium, etc. An + * {@link #Unknown} element has no symbol. + * + * @return the symbol + */ + public String symbol() { + return symbol; + } + + /** + * Return the period in the periodic table this element belongs to. If + * the element is {@link #Unknown} it's period is 0. + * + * @return a period in the periodic table + */ + public int period() { + return period; + } + + /** + * Return the group in the periodic table this element belongs to. If + * the element does not belong to a group then it's group is '0'. + * + * @return a group in the periodic table + */ + public int group() { + return group; + } + + /** + * The covalent radius, rcov, is a measure of the + * size of an atom that forms part of one covalent bond. + * + * @return covalent radius - null if not available + * @see Covalent radius + */ + public Double covalentRadius() { + return rCov; + } + + /** + * The van der Waals radius, rw, of an atom is the + * radius of an imaginary hard sphere which can be used to model the + * atom. + * + * @return van der Waals radius - null if not available + * @see Van de Waals radius + */ + public Double vdwRadius() { + return rW; + } + + /** + * Electronegativity, symbol χ, is a chemical property that describes + * the tendency of an atom or a functional group to attract electrons + * (or electron density) towards itself. This method provides access to the + * Pauling electronegativity value for a chemical element. If no value is + * available 'null' is returned. + * + * @return Pauling electronegativity - null if not available + * @see Pauling Electronegativity + */ + public Double electronegativity() { + return electronegativity; + } + + /** + * Access an {@link IElement} instance of the chemical element. + * + * @return an instance + */ + public IElement toIElement() { + return instance; + } + + /** + * Obtain the element with the specified atomic number. If no element had + * the specified atomic number then {@link #Unknown} is returned. + * + *
+     *     // carbon
+     *     Elements e = Elements.ofNumber(6);
+     *
+     *     // oxygen
+     *     Elements e = Elements.ofNumber(8);
+     * 
+ * + * @param number atomic number + * @return an element, or {@link #Unknown} + */ + public static Elements ofNumber(final int number) { + if (number < 0 || number > 118) return Unknown; + return NUMER_MAP[number]; + } + + /** + * Obtain the element with the specified symbol or name. If no element had + * the specified symbol or name then {@link #Unknown} is returned. The + * input is case-insensitive. + * + *
+     *     // carbon
+     *     Elements e = Elements.ofString("c");
+     *     Elements e = Elements.ofString("C");
+     *     Elements e = Elements.ofString("Carbon");
+     *     Elements e = Elements.ofString("carbon");
+     * 
+ * + * @param str input string + * @return an element, or {@link #Unknown} + */ + public static Elements ofString(final String str) { + if (str == null) return Unknown; + Elements e = SYMBOL_MAP.get(str.toLowerCase(Locale.ENGLISH)); + if (e == null) return Unknown; + return e; + } + + /** These instances are for backards compatability. */ + public final static IElement DUMMY = Unknown.toIElement(); + public final static IElement HYDROGEN = Hydrogen.toIElement(); + public final static IElement HELIUM = Helium.toIElement(); + public final static IElement LITHIUM = Lithium.toIElement(); + public final static IElement BERYLLIUM = Beryllium.toIElement(); + public final static IElement BORON = Boron.toIElement(); + public final static IElement CARBON = Carbon.toIElement(); + public final static IElement NITROGEN = Nitrogen.toIElement(); + public final static IElement OXYGEN = Oxygen.toIElement(); + public final static IElement FLUORINE = Fluorine.toIElement(); + public final static IElement NEON = Neon.toIElement(); + public final static IElement SODIUM = Sodium.toIElement(); + public final static IElement MAGNESIUM = Magnesium.toIElement(); + public final static IElement ALUMINIUM = Aluminium.toIElement(); + public final static IElement SILICON = Silicon.toIElement(); + public final static IElement PHOSPHORUS = Phosphorus.toIElement(); + public final static IElement SULFUR = Sulfur.toIElement(); + public final static IElement CHLORINE = Chlorine.toIElement(); + public final static IElement ARGON = Argon.toIElement(); + public final static IElement POTASSIUM = Potassium.toIElement(); + public final static IElement CALCIUM = Calcium.toIElement(); + public final static IElement SCANDIUM = Scandium.toIElement(); + public final static IElement TITANIUM = Titanium.toIElement(); + public final static IElement VANADIUM = Vanadium.toIElement(); + public final static IElement CHROMIUM = Chromium.toIElement(); + public final static IElement MANGANESE = Manganese.toIElement(); + public final static IElement IRON = Iron.toIElement(); + public final static IElement COBALT = Cobalt.toIElement(); + public final static IElement NICKEL = Nickel.toIElement(); + public final static IElement COPPER = Copper.toIElement(); + public final static IElement ZINC = Zinc.toIElement(); + public final static IElement GALLIUM = Gallium.toIElement(); + public final static IElement GERMANIUM = Germanium.toIElement(); + public final static IElement ARSENIC = Arsenic.toIElement(); + public final static IElement SELENIUM = Selenium.toIElement(); + public final static IElement BROMINE = Bromine.toIElement(); + public final static IElement KRYPTON = Krypton.toIElement(); + public final static IElement RUBIDIUM = Rubidium.toIElement(); + public final static IElement STRONTIUM = Strontium.toIElement(); + public final static IElement YTTRIUM = Yttrium.toIElement(); + public final static IElement ZIRCONIUM = Zirconium.toIElement(); + public final static IElement NIOBIUM = Niobium.toIElement(); + public final static IElement MOLYBDENUM = Molybdenum.toIElement(); + public final static IElement TECHNETIUM = Technetium.toIElement(); + public final static IElement RUTHENIUM = Ruthenium.toIElement(); + public final static IElement RHODIUM = Rhodium.toIElement(); + public final static IElement PALLADIUM = Palladium.toIElement(); + public final static IElement SILVER = Silver.toIElement(); + public final static IElement CADMIUM = Cadmium.toIElement(); + public final static IElement INDIUM = Indium.toIElement(); + public final static IElement TIN = Tin.toIElement(); + public final static IElement ANTIMONY = Antimony.toIElement(); + public final static IElement TELLURIUM = Tellurium.toIElement(); + public final static IElement IODINE = Iodine.toIElement(); + public final static IElement XENON = Xenon.toIElement(); + public final static IElement CAESIUM = Caesium.toIElement(); + public final static IElement BARIUM = Barium.toIElement(); + public final static IElement LANTHANUM = Lanthanum.toIElement(); + public final static IElement CERIUM = Cerium.toIElement(); + public final static IElement PRASEODYMIUM = Praseodymium.toIElement(); + public final static IElement NEODYMIUM = Neodymium.toIElement(); + public final static IElement PROMETHIUM = Promethium.toIElement(); + public final static IElement SAMARIUM = Samarium.toIElement(); + public final static IElement EUROPIUM = Europium.toIElement(); + public final static IElement GADOLINIUM = Gadolinium.toIElement(); + public final static IElement TERBIUM = Terbium.toIElement(); + public final static IElement DYSPROSIUM = Dysprosium.toIElement(); + public final static IElement HOLMIUM = Holmium.toIElement(); + public final static IElement ERBIUM = Erbium.toIElement(); + public final static IElement THULIUM = Thulium.toIElement(); + public final static IElement YTTERBIUM = Ytterbium.toIElement(); + public final static IElement LUTETIUM = Lutetium.toIElement(); + public final static IElement HAFNIUM = Hafnium.toIElement(); + public final static IElement TANTALUM = Tantalum.toIElement(); + public final static IElement TUNGSTEN = Tungsten.toIElement(); + public final static IElement RHENIUM = Rhenium.toIElement(); + public final static IElement OSMIUM = Osmium.toIElement(); + public final static IElement IRIDIUM = Iridium.toIElement(); + public final static IElement PLATINUM = Platinum.toIElement(); + public final static IElement GOLD = Gold.toIElement(); + public final static IElement MERCURY = Mercury.toIElement(); + public final static IElement THALLIUM = Thallium.toIElement(); + public final static IElement LEAD = Lead.toIElement(); + public final static IElement BISMUTH = Bismuth.toIElement(); + public final static IElement POLONIUM = Polonium.toIElement(); + public final static IElement ASTATINE = Astatine.toIElement(); + public final static IElement RADON = Radon.toIElement(); + public final static IElement FRANCIUM = Francium.toIElement(); + public final static IElement RADIUM = Radium.toIElement(); + public final static IElement ACTINIUM = Actinium.toIElement(); + public final static IElement THORIUM = Thorium.toIElement(); + public final static IElement PROTACTINIUM = Protactinium.toIElement(); + public final static IElement URANIUM = Uranium.toIElement(); + public final static IElement NEPTUNIUM = Neptunium.toIElement(); + public final static IElement PLUTONIUM = Plutonium.toIElement(); + public final static IElement AMERICIUM = Americium.toIElement(); + public final static IElement CURIUM = Curium.toIElement(); + public final static IElement BERKELIUM = Berkelium.toIElement(); + public final static IElement CALIFORNIUM = Californium.toIElement(); + public final static IElement EINSTEINIUM = Einsteinium.toIElement(); + public final static IElement FERMIUM = Fermium.toIElement(); + public final static IElement MENDELEVIUM = Mendelevium.toIElement(); + public final static IElement NOBELIUM = Nobelium.toIElement(); + public final static IElement LAWRENCIUM = Lawrencium.toIElement(); + public final static IElement RUTHERFORDIUM = Rutherfordium.toIElement(); + public final static IElement DUBNIUM = Dubnium.toIElement(); + public final static IElement SEABORGIUM = Seaborgium.toIElement(); + public final static IElement BOHRIUM = Bohrium.toIElement(); + public final static IElement HASSIUM = Hassium.toIElement(); + public final static IElement MEITNERIUM = Meitnerium.toIElement(); + public final static IElement DARMSTADTIUM = Darmstadtium.toIElement(); + public final static IElement ROENTGENIUM = Roentgenium.toIElement(); + public final static IElement UNUNBIUM = Copernicium.toIElement(); + public final static IElement UNUNTRIUM = Ununtrium.toIElement(); + public final static IElement UNUNQUADIUM = Flerovium.toIElement(); + public final static IElement FLEROVIUM = Flerovium.toIElement(); + public final static IElement UNUNPENTIUM = Ununpentium.toIElement(); + public final static IElement UNUNHEXIUM = Livermorium.toIElement(); + public final static IElement LIVERMORIUM = Livermorium.toIElement(); + + // Incorrect spelling + @Deprecated + public final static IElement PLUTOMNIUM = PLUTONIUM; +} diff --git a/pdbbind/atomic_feature.py b/pdbbind/atomic_feature.py new file mode 100644 index 0000000..ab32324 --- /dev/null +++ b/pdbbind/atomic_feature.py @@ -0,0 +1,429 @@ +# For OctSurfNet, get the atomic features for pocket and ligand. +# OpenBabel 3.0.0 also support van der waal surface for molecules. + +import pickle + +import numpy as np +# import pybel +# import openbabel +from openbabel import pybel +from openbabel import openbabel as ob + + +# from math import ceil, sin, cos, sqrt, pi +# from itertools import combinations +import os + + +def get_cdk_vdwr_dic(file = '/home/qil15006/2020Summer/OCNN_Jun26_2020/pdbbind/Elements.java'): #/home/qil15006/2020Summer/OCNN_Jun26_2020/pdbbind/Elements.java + """extract van der waal radius from cdk Element.java file""" + cdk_vdwr_dic = {} + with open(file, 'r') as f: + lines = f.readlines() + start = False + for line in lines: + if 'public enum Elements {' in line: + start = True + continue + if start: + if '@Deprecated' in line: + continue + contents = line.split('(') + contents = contents[1] + row = contents.split(',') + atomic_number = int(row[0]) + radius = row[-3] + if 'null' in radius: + # print(line) + pass + else: + cdk_vdwr_dic[atomic_number] = float(radius) + if 'Oganesson' in line: + break + else: + continue + return cdk_vdwr_dic +cdk_vdwr_dic = get_cdk_vdwr_dic() +# print(cdk_vdwr_dic) + +class Featurizer(): + """Calcaulates atomic features for molecules. Features can encode atom type, + native pybel properties or any property defined with SMARTS patterns + + Attributes + ---------- + FEATURE_NAMES: list of strings + Labels for features (in the same order as features) + NUM_ATOM_CLASSES: int + Number of atom codes + ATOM_CODES: dict + Dictionary mapping atomic numbers to codes + NAMED_PROPS: list of string + Names of atomic properties to retrieve from pybel.Atom object + CALLABLES: list of callables + Callables used to calculcate custom atomic properties + SMARTS: list of SMARTS strings + SMARTS patterns defining additional atomic properties + """ + + def __init__(self, atom_codes=None, atom_labels=None, + named_properties=None, save_molecule_codes=True, + custom_properties=None, smarts_properties=None, + smarts_labels=None): + + """Creates Featurizer with specified types of features. Elements of a + feature vector will be in a following order: atom type encoding + (defined by atom_codes), Pybel atomic properties (defined by + named_properties), molecule code (if present), custom atomic properties + (defined `custom_properties`), and additional properties defined with + SMARTS (defined with `smarts_properties`). + + Parameters + ---------- + atom_codes: dict, optional + Dictionary mapping atomic numbers to codes. It will be used for + one-hot encoging therefore if n different types are used, codes + shpuld be from 0 to n-1. Multiple atoms can have the same code, + e.g. you can use {6: 0, 7: 1, 8: 1} to encode carbons with [1, 0] + and nitrogens and oxygens with [0, 1] vectors. If not provided, + default encoding is used. + atom_labels: list of strings, optional + Labels for atoms codes. It should have the same length as the + number of used codes, e.g. for `atom_codes={6: 0, 7: 1, 8: 1}` you + should provide something like ['C', 'O or N']. If not specified + labels 'atom0', 'atom1' etc are used. If `atom_codes` is not + specified this argument is ignored. + named_properties: list of strings, optional + Names of atomic properties to retrieve from pybel.Atom object. If + not specified ['hyb', 'heavyvalence', 'heterovalence', + 'partialcharge'] is used. + save_molecule_codes: bool, optional (default True) + If set to True, there will be an additional feature to save + molecule code. It is usefeul when saving molecular complex in a + single array. + custom_properties: list of callables, optional + Custom functions to calculate atomic properties. Each element of + this list should be a callable that takes pybel.Atom object and + returns a float. If callable has `__name__` property it is used as + feature label. Otherwise labels 'func' etc are used, where i is + the index in `custom_properties` list. + smarts_properties: list of strings, optional + Additional atomic properties defined with SMARTS patterns. These + patterns should match a single atom. If not specified, deafult + patterns are used. + smarts_labels: list of strings, optional + Labels for properties defined with SMARTS. Should have the same + length as `smarts_properties`. If not specified labels 'smarts0', + 'smarts1' etc are used. If `smarts_properties` is not specified + this argument is ignored. + """ + + # Remember namse of all features in the correct order + self.FEATURE_NAMES = [] + + if atom_codes is not None: + if not isinstance(atom_codes, dict): + raise TypeError('Atom codes should be dict, got %s instead' + % type(atom_codes)) + codes = set(atom_codes.values()) + for i in range(len(codes)): + if i not in codes: + raise ValueError('Incorrect atom code %s' % i) + + self.NUM_ATOM_CLASSES = len(codes) + self.ATOM_CODES = atom_codes + if atom_labels is not None: + if len(atom_labels) != self.NUM_ATOM_CLASSES: + raise ValueError('Incorrect number of atom labels: ' + '%s instead of %s' + % (len(atom_labels), self.NUM_ATOM_CLASSES)) + else: + atom_labels = ['atom%s' % i for i in range(self.NUM_ATOM_CLASSES)] + self.FEATURE_NAMES += atom_labels + else: + self.ATOM_CODES = {} + + metals = ([3, 4, 11, 12, 13] + list(range(19, 32)) + + list(range(37, 51)) + list(range(55, 84)) + + list(range(87, 104))) + + # List of tuples (atomic_num, class_name) with atom types to encode. + atom_classes = [ + (1, 'H'), # QQ add: H also considered in surface. + (5, 'B'), + (6, 'C'), + (7, 'N'), + (8, 'O'), + (15, 'P'), + (16, 'S'), + (34, 'Se'), + ([9, 17, 35, 53], 'halogen'), + (metals, 'metal') + ] + + for code, (atom, name) in enumerate(atom_classes): + if type(atom) is list: + for a in atom: + self.ATOM_CODES[a] = code + else: + self.ATOM_CODES[atom] = code + self.FEATURE_NAMES.append(name) + + self.NUM_ATOM_CLASSES = len(atom_classes) + + if named_properties is not None: + if not isinstance(named_properties, (list, tuple, np.ndarray)): + raise TypeError('named_properties must be a list') + allowed_props = [prop for prop in dir(pybel.Atom) + if not prop.startswith('__')] + for prop_id, prop in enumerate(named_properties): + if prop not in allowed_props: + raise ValueError( + 'named_properties must be in pybel.Atom attributes,' + ' %s was given at position %s' % (prop_id, prop) + ) + self.NAMED_PROPS = named_properties + else: + # pybel.Atom properties to save + self.NAMED_PROPS = ['hyb', 'heavyvalence', 'heterovalence', + 'partialcharge', 'radius'] + self.FEATURE_NAMES += self.NAMED_PROPS + + if not isinstance(save_molecule_codes, bool): + raise TypeError('save_molecule_codes should be bool, got %s ' + 'instead' % type(save_molecule_codes)) + self.save_molecule_codes = save_molecule_codes + if save_molecule_codes: + # Remember if an atom belongs to the ligand or to the protein + self.FEATURE_NAMES.append('molcode') + + self.CALLABLES = [] + if custom_properties is not None: + for i, func in enumerate(custom_properties): + if not callable(func): + raise TypeError('custom_properties should be list of' + ' callables, got %s instead' % type(func)) + name = getattr(func, '__name__', '') + if name == '': + name = 'func%s' % i + self.CALLABLES.append(func) + self.FEATURE_NAMES.append(name) + + if smarts_properties is None: + # SMARTS definition for other properties + self.SMARTS = [ + '[#6+0!$(*~[#7,#8,F]),SH0+0v2,s+0,S^3,Cl+0,Br+0,I+0]', + '[a]', + '[!$([#1,#6,F,Cl,Br,I,o,s,nX3,#7v5,#15v5,#16v4,#16v6,*+1,*+2,*+3])]', + '[!$([#6,H0,-,-2,-3]),$([!H0;#7,#8,#9])]', + '[r]' + ] + smarts_labels = ['hydrophobic', 'aromatic', 'acceptor', 'donor', + 'ring'] + elif not isinstance(smarts_properties, (list, tuple, np.ndarray)): + raise TypeError('smarts_properties must be a list') + else: + self.SMARTS = smarts_properties + + if smarts_labels is not None: + if len(smarts_labels) != len(self.SMARTS): + raise ValueError('Incorrect number of SMARTS labels: %s' + ' instead of %s' + % (len(smarts_labels), len(self.SMARTS))) + else: + smarts_labels = ['smarts%s' % i for i in range(len(self.SMARTS))] + + # Compile patterns + self.compile_smarts() + self.FEATURE_NAMES += smarts_labels + + def compile_smarts(self): + self.__PATTERNS = [] + for smarts in self.SMARTS: + self.__PATTERNS.append(pybel.Smarts(smarts)) + + def encode_num(self, atomic_num): + """Encode atom type with a binary vector. If atom type is not included in + the `atom_classes`, its encoding is an all-zeros vector. + + Parameters + ---------- + atomic_num: int + Atomic number + + Returns + ------- + encoding: np.ndarray + Binary vector encoding atom type (one-hot or null). + """ + + if not isinstance(atomic_num, int): + raise TypeError('Atomic number must be int, %s was given' + % type(atomic_num)) + + encoding = np.zeros(self.NUM_ATOM_CLASSES) + try: + encoding[self.ATOM_CODES[atomic_num]] = 1.0 + except: + pass + return encoding + + def find_smarts(self, molecule): + """Find atoms that match SMARTS patterns. + + Parameters + ---------- + molecule: pybel.Molecule + + Returns + ------- + features: np.ndarray + NxM binary array, where N is the number of atoms in the `molecule` + and M is the number of patterns. `features[i, j]` == 1.0 if i'th + atom has j'th property + """ + + if not isinstance(molecule, pybel.Molecule): + raise TypeError('molecule must be pybel.Molecule object, %s was given' + % type(molecule)) + + features = np.zeros((len(molecule.atoms), len(self.__PATTERNS))) + + for (pattern_id, pattern) in enumerate(self.__PATTERNS): + atoms_with_prop = np.array(list(*zip(*pattern.findall(molecule))), + dtype=int) - 1 + features[atoms_with_prop, pattern_id] = 1.0 + return features + + def get_features(self, molecule, molcode=None): + """Get coordinates and features for all heavy atoms in the molecule. + + Parameters + ---------- + molecule: pybel.Molecule + molcode: float, optional + Molecule type. You can use it to encode whether an atom belongs to + the ligand (1.0) or to the protein (-1.0) etc. + + Returns + ------- + coords: np.ndarray, shape = (N, 3) + Coordinates of all heavy atoms in the `molecule`. + features: np.ndarray, shape = (N, F) + Features of all heavy atoms in the `molecule`: atom type + (one-hot encoding), pybel.Atom attributes, type of a molecule + (e.g protein/ligand distinction), and other properties defined with + SMARTS patterns + """ + + if not isinstance(molecule, pybel.Molecule): + raise TypeError('molecule must be pybel.Molecule object,' + ' %s was given' % type(molecule)) + if molcode is None: + if self.save_molecule_codes is True: + raise ValueError('save_molecule_codes is set to True,' + ' you must specify code for the molecule') + elif not isinstance(molcode, (float, int)): + raise TypeError('motlype must be float, %s was given' + % type(molcode)) + + coords = [] + features = [] + heavy_atoms = [] + + for i, atom in enumerate(molecule): + # ignore hydrogens and dummy atoms (they have atomicnum set to 0) + # if atom.atomicnum > 1: + # heavy_atoms.append(i) + # coords.append(atom.coords) + # + # features.append(np.concatenate(( + # self.encode_num(atom.atomicnum), + # [atom.__getattribute__(prop) for prop in self.NAMED_PROPS], + # [func(atom) for func in self.CALLABLES], + # ))) + + heavy_atoms.append(i) + coords.append(atom.coords) + + # ['hyb', 'heavyvalence', 'heterovalence', + # 'partialcharge'] + atom_attributes = [] + for prop in self.NAMED_PROPS: + if prop == 'hyb': + atom_attributes.append(atom.hyb) + if prop == 'heavyvalence': + atom_attributes.append(atom.heavydegree) + if prop == 'heterovalence': + atom_attributes.append(atom.heterodegree) + if prop == 'partialcharge': + atom_attributes.append(atom.partialcharge) + if prop == 'radius': + temp = atom.atomicnum + # temp2 = ob.GetVdwRad(temp) + # if abs(temp2 - 1.1) < 0.01: + # temp2 = 1.2 + temp2 = cdk_vdwr_dic[temp] + atom_attributes.append(temp2) + pass + + features.append(np.concatenate(( + self.encode_num(atom.atomicnum), + # [atom.__getattribute__(prop) for prop in self.NAMED_PROPS], + atom_attributes, + [func(atom) for func in self.CALLABLES], + ))) + + coords = np.array(coords, dtype=np.float32) + features = np.array(features, dtype=np.float32) + if self.save_molecule_codes: + features = np.hstack((features, + molcode * np.ones((len(features), 1)))) + + features = np.hstack([features, + self.find_smarts(molecule)[heavy_atoms]]) + + if np.isnan(features).any(): + raise RuntimeError('Got NaN when calculating features') + + return coords, features + +def write_feature_file(folder): + contents = folder.split('/') + id = contents[-1] + ligand_file = folder + '/{}_ligand.mol2'.format(id) + pocket_file = folder + '/{}_pocket.pdb'.format(id) + + ligand = next(pybel.readfile('mol2', ligand_file)) + featurizer = Featurizer() + ligand_coords, ligand_feature = featurizer.get_features(ligand, molcode=0) + with open('{}/{}_ligand_feature.txt'.format(folder,id), 'w') as f: + for i in range(ligand_feature.shape[0]): + # f.writelines('{} {}\n'.format(i+1, ligand_feature[i].tolist())) + f.writelines('{}'.format(i+1)) + for j in ligand_coords[i]: + f.writelines(' {}'.format(j)) + for j in ligand_feature[i]: + f.writelines(' {}'.format(j)) + f.writelines('\n') + + pocket = next(pybel.readfile('pdb', pocket_file)) + featurizer = Featurizer() + pocket_coords, pocket_feature = featurizer.get_features(pocket, molcode=1) + with open('{}/{}_pocket_feature.txt'.format(folder, id), 'w') as f: + for i in range(pocket_feature.shape[0]): + # f.writelines('{} {}\n'.format(i+1, pocket_feature[i].tolist())) + f.writelines('{}'.format(i+1)) + for j in pocket_coords[i]: + f.writelines(' {}'.format(j)) + for j in pocket_feature[i]: + f.writelines(' {}'.format(j)) + f.writelines('\n') + pass + +if __name__ == "__main__": + path = os.getcwd() + write_feature_file(path) + +# path = '../test_set/4oem' #'/media/data/data_share/pdbbind/v2018-other-PL/3oc0' #'/media/data/data_share/pdbbind/CASF-2016/coreset/3wtj'# #'../test_set/1a4r' +# write_feature_file(path) diff --git a/pdbbind/check_points_file.py b/pdbbind/check_points_file.py new file mode 100644 index 0000000..fd903ff --- /dev/null +++ b/pdbbind/check_points_file.py @@ -0,0 +1,29 @@ +import os + +cwd = os.getcwd() +folders = os.listdir(cwd) + +def check_name_points(filename): + if 'points.points' in filename: + return True + +count = 0 +for folder in folders: + current_folder = os.path.join(cwd, folder) + if os.path.isfile(current_folder): + continue + subfolders = os.listdir(current_folder) + find = False + for sub in subfolders: + if check_name_points(sub): + size = os.stat(os.path.join(current_folder, sub)).st_size + if size < 10000000: + print('File size too small: {}'.format(folder)) + find = True + break + if not find: + print('Not find points in: {}'.format(folder)) + count += 1 + +print('Total not found: {}'.format(count)) + diff --git a/pdbbind/chesk_make_grids.py b/pdbbind/chesk_make_grids.py new file mode 100644 index 0000000..73b6e01 --- /dev/null +++ b/pdbbind/chesk_make_grids.py @@ -0,0 +1,23 @@ +import numpy as np + +size = 64 +channel = 24 +a = np.zeros(size**3*channel) + + +j = 12 +x,y,z = 15,26,27 + + +idx = j * size **3 + x * size**2 + y * size + z +a[idx] = 1 + +b = a.reshape(channel, size, size, size) +b.nonzero() + +import tensorflow as tf +c = tf.convert_to_tensor(a) +c = tf.reshape(c, (channel, size, size, size)) +sess = tf.InteractiveSession() +c[j, x, y, z].eval() + diff --git a/pdbbind/data_download.sh b/pdbbind/data_download.sh new file mode 100644 index 0000000..5d271e0 --- /dev/null +++ b/pdbbind/data_download.sh @@ -0,0 +1,28 @@ +mkdir data_folder +cd data_folder + +wget "http://www.pdbbind.org.cn/download/pdbbind_v2018_refined.tar.gz" +wget "http://www.pdbbind.org.cn/download/pdbbind_v2018_other_PL.tar.gz" +wget "http://www.pdbbind-cn.org/download/CASF-2016.tar.gz" + +tar -xzvf pdbbind_v2018_refined.tar.gz +tar -xzvf pdbbind_v2018_other_PL.tar.gz +tar -xzvf CASF-2016.tar.gz + +cp ../bash_scripts/* v2018-other-PL +cp ../bash_scripts/* refined-set +cp ../bash_scripts/* CASF-2016/coreset + +cd v2018-other-PL +bash correct_mol2.sh +bash data_clean_index0.sh +#bash data_prepare_points.sh +# +cd ../refined-set +bash data_clean_index0.sh +#bash data_prepare_points.sh +# +cd ../CASF-2016/coreset +bash data_clean_index0.sh +#bash data_prepare_points.sh + diff --git a/pdbbind/generate_octree_list.py b/pdbbind/generate_octree_list.py new file mode 100644 index 0000000..23a615d --- /dev/null +++ b/pdbbind/generate_octree_list.py @@ -0,0 +1,383 @@ +import re +import math +import numpy as np +import random +import os +import pandas as pd + + + +def filter(folders, feature_need_sdf): + results = [] + for folder in folders: + if len(folder) != 4: + continue + if folder in feature_need_sdf: + continue + if os.path.isfile(folder): + print(folder) + continue + results.append(folder) + return results + +def check_duplicate(folder1, folder2): + results = [] + for folder in folder1: + if folder in folder2: + # print('folder1 subfolder {} also in folder2'.format(folder)) + continue + results.append(folder) + for folder in folder2: + if folder in folder1: + # print('folder2 subfolder {} also in folder1'.format(folder)) + pass + return results + +# use to check if all the compelx show in PL_data include pdb and mol2 file. Answer is NO. +def check_dic_id(dic, general_folder, refine_folder, core_folder): + for key in dic: + if key not in general_folder and key not in refine_folder and key not in core_folder: + print(key) + + +def read_affinity(file_name, mode): + """ + read affinity data from pdbbind index file, refers to log Kd/Ki + Benefit is: in general set, there are some <,>,~ in Kd/Ki, directly use log Kd/Ki might be better. + """ + record_dic = {} + with open(file_name, 'r') as data_fid: + for line in data_fid: + if '#' in line or '===' in line or len(line) == 0: + continue + line = re.sub('\s+', ' ', line).strip() + contents = line.split(' ') + id = contents[0] + affinity = float(contents[3]) + if mode == 'reg': + record_dic[id] = affinity + else: + if affinity > 6 + math.log10(5): + record_dic[id] = 1 + elif affinity < 6 - math.log10(5): + record_dic[id] = 0 + else: + print('skip {}, affinity is {}.'.format(id, affinity)) + return record_dic + +# train/test/val split, with all pdbbind data. +def write_octree_list(general_dic, core_folders, refined_folders, depth = 8, mode = 'reg'): + # test only include core set + test_file_name = root_folder + 'octree_list_test_{}_{}.txt'.format(depth, mode) + test_affinity = [] + with open(test_file_name, 'w') as f: + for id in core_folders: + if id not in general_dic: + continue + for v in range(0, view_num): + path = 'CASF-2016/coreset/{0}/octree_folder/{1}_points_{2}_2_{3:03d}.octree'.format(id, id, depth, v) + line = '{} {}\n'.format(path, general_dic[id]) + f.write(line) + test_affinity.append(general_dic[id]) + + # validation is part of refined set + random.seed(2020) + val_id = random.sample(refined_folders, k=600) + val_file_name = root_folder + 'octree_list_val_{}_{}.txt'.format(depth, mode) + val_affinity = [] + with open(val_file_name, 'w') as f: + for id in refined_folders: + if id not in val_id: + continue + if id not in general_dic: + print('id {} not in general_dic'.format(id)) + continue + for v in range(0, view_num): + path = refined_set + '/{0}/octree_folder/{1}_points_{2}_2_{3:03d}.octree'.format(id, id, depth, v) + line = '{} {}\n'.format(path, general_dic[id]) + f.write(line) + val_affinity.append(general_dic[id]) + + train_file_name = root_folder + 'octree_list_train_{}_{}.txt'.format(depth, mode) + train_affinity = [] + with open(train_file_name, 'w') as f: + train_total = general_folders + refined_folders + for id in train_total: + if id in val_id: + continue + if id not in general_dic: + continue + for v in range(0, view_num): + if id in refined_folders: + path = refined_set + '/{0}/octree_folder/{1}_points_{2}_2_{3:03d}.octree'.format(id, id, depth, v) + else: + path = 'v2018-other-PL/{0}/octree_folder/{1}_points_{2}_2_{3:03d}.octree'.format(id, id, depth, v) + line = '{} {}\n'.format(path, general_dic[id]) + f.write(line) + train_affinity.append(general_dic[id]) + + train_affinity = np.array(train_affinity) + val_affinity = np.array(val_affinity) + test_affinity = np.array(test_affinity) + print('train_affinity size: {}, mean: {}, std: {}'.format(train_affinity.shape[0], np.mean(train_affinity), np.std(train_affinity))) + print('val_affinity size: {}, mean: {}, std: {}'.format(val_affinity.shape[0], np.mean(val_affinity), np.std(val_affinity))) + print('test_affinity size: {}, mean: {}, std: {}'.format(test_affinity.shape[0], np.mean(test_affinity), np.std(test_affinity))) + +def write_points_list(general_dic, core_folders, refined_folders, mode = 'reg', density = None): + # test only include core set + test_file_name = root_folder + 'points_list_test_{}.txt'.format(mode) + test_affinity = [] + with open(test_file_name, 'w') as f: + for id in core_folders: + if id not in general_dic: + continue + if density is None: + path = 'CASF-2016/coreset/{0}/{1}_points.points'.format(id, id) + else: + path = 'CASF-2016/coreset/{0}/{1}_points_{2}.points'.format(id, id, density) + # print(path) + line = '{} {}\n'.format(path, general_dic[id]) + f.write(line) + test_affinity.append(general_dic[id]) + + # validation is part of refined set + random.seed(2020) + # val_id = random.choices(refined_folders, k=1000) + val_id = random.sample(refined_folders, k=600) + val_file_name = root_folder + 'points_list_val_{}.txt'.format(mode) + val_affinity = [] + with open(val_file_name, 'w') as f: + for id in refined_folders: + if id not in val_id or id not in general_dic: + continue + if density is None: + path = refined_set + '/{0}/{1}_points.points'.format(id, id) + else: + path = refined_set + '/{0}/{1}_points_{2}.points'.format(id, id, density) + + line = '{} {}\n'.format(path, general_dic[id]) + f.write(line) + val_affinity.append(general_dic[id]) + + + + train_file_name = root_folder + 'points_list_train_{}.txt'.format(mode) + train_affinity = [] + with open(train_file_name, 'w') as f: + train_total = general_folders + refined_folders + for id in train_total: + if id in val_id: + continue + if id not in general_dic: + continue + if id in refined_folders: + if density is None: + path = refined_set + '/{0}/{1}_points.points'.format(id, id) + else: + path = refined_set + '/{0}/{1}_points_{2}.points'.format(id, id, density) + else: + if density is None: + path = 'v2018-other-PL/{0}/{1}_points.points'.format(id, id) + else: + path = 'v2018-other-PL/{0}/{1}_points_{2}.points'.format(id, id, density) + + # print(path) + line = '{} {}\n'.format(path, general_dic[id]) + f.write(line) + train_affinity.append(general_dic[id]) + + train_affinity = np.array(train_affinity) + val_affinity = np.array(val_affinity) + test_affinity = np.array(test_affinity) + print('train_affinity size: {}, mean: {}, std: {}'.format(train_affinity.shape[0], np.mean(train_affinity), np.std(train_affinity))) + print('val_affinity size: {}, mean: {}, std: {}'.format(val_affinity.shape[0], np.mean(val_affinity), np.std(val_affinity))) + print('test_affinity size: {}, mean: {}, std: {}'.format(test_affinity.shape[0], np.mean(test_affinity), np.std(test_affinity))) + + +def write_points_list_refine(general_dic, core_folders, refined_folders, mode='reg', density = None): + # test only include core set + test_file_name = root_folder + 'points_list_test_refine_{}.txt'.format(mode) + test_affinity = [] + with open(test_file_name, 'w') as f: + for id in core_folders: + if id not in general_dic: + continue + if density is None: + path = 'CASF-2016/coreset/{0}/{1}_points.points'.format(id, id) + else: + path = 'CASF-2016/coreset/{0}/{1}_points_{2}.points'.format(id, id, density) + line = '{} {}\n'.format(path, general_dic[id]) + f.write(line) + test_affinity.append(general_dic[id]) + + # validation is part of refined set + random.seed(2020) + # val_id = random.choices(refined_folders, k=1000) + val_id = random.sample(refined_folders, k=300) + val_file_name = root_folder + 'points_list_val_refine_{}.txt'.format(mode) + val_affinity = [] + with open(val_file_name, 'w') as f: + for id in refined_folders: + if id not in val_id or id not in general_dic: + continue + if density is None: + path = refined_set + '/{0}/{1}_points.points'.format(id, id) + else: + path = refined_set + '/{0}/{1}_points_{2}.points'.format(id, id, density) + + line = '{} {}\n'.format(path, general_dic[id]) + f.write(line) + val_affinity.append(general_dic[id]) + + train_file_name = root_folder + 'points_list_train_refine_{}.txt'.format(mode) + train_affinity = [] + with open(train_file_name, 'w') as f: + train_total = refined_folders + for id in train_total: + if id not in general_dic: + continue + if id in val_id: + continue + if id in refined_folders: + if density is None: + path = refined_set + '/{0}/{1}_points.points'.format(id, id) + else: + path = refined_set + '/{0}/{1}_points_{2}.points'.format(id, id, density) + line = '{} {}\n'.format(path, general_dic[id]) + f.write(line) + train_affinity.append(general_dic[id]) + + train_affinity = np.array(train_affinity) + val_affinity = np.array(val_affinity) + test_affinity = np.array(test_affinity) + print('train shape: ', train_affinity.shape) + print('val shape: ', val_affinity.shape) + print('test shape: ', test_affinity.shape) + print('train_affinity mean: {}, std: {}'.format(np.mean(train_affinity), np.std(train_affinity))) + print('val_affinity mean: {}, std: {}'.format(np.mean(val_affinity), np.std(val_affinity))) + print('test_affinity mean: {}, std: {}'.format(np.mean(test_affinity), np.std(test_affinity))) + + +def write_octree_list_refine(general_dic, core_folders, refined_folders, depth = 8, mode='reg'): + # test only include core set + test_file_name = root_folder + 'octree_list_core_{}_{}.txt'.format(depth, mode) + test_affinity = [] + with open(test_file_name, 'w') as f: + for id in core_folders: + if id not in general_dic: + continue + # for v in range(0, view_num): + for v in range(15, 16): + path = 'CASF-2016/coreset/{0}/octree_folder/{1}_points_{2}_2_{3:03d}.octree'.format(id, id, depth, v) + line = '{} {}\n'.format(path, general_dic[id]) + f.write(line) + test_affinity.append(general_dic[id]) + + train_file_name = root_folder + 'octree_list_refine_{}_{}.txt'.format(depth, mode) + train_affinity = [] + with open(train_file_name, 'w') as f: + train_total = refined_folders + for id in train_total: + if id not in general_dic: + continue + # for v in range(0, view_num): + for v in range(15, 16): + if id in refined_folders: + path = refined_set + '/{0}/octree_folder/{1}_points_{2}_2_{3:03d}.octree'.format(id, id, depth, v) + # else: + # path = 'v2018-other-PL/{0}/{1}_points_{2}_2_{3:03d}.octree'.format(id, id, depth, v) + line = '{} {}\n'.format(path, general_dic[id]) + f.write(line) + train_affinity.append(general_dic[id]) + + train_affinity = np.array(train_affinity) + test_affinity = np.array(test_affinity) + print('train shape: ', train_affinity.shape) + print('test shape: ', test_affinity.shape) + print('train_affinity mean: {}, std: {}'.format(np.mean(train_affinity), np.std(train_affinity))) + print('test_affinity mean: {}, std: {}'.format(np.mean(test_affinity), np.std(test_affinity))) + +def list_astex_complex(general_dic): + folder = '/media/data/data_share/qil15006/astex_diverse_set' + complexes = os.listdir(folder) + complexes = [item for item in complexes if len(item)==4 ] + + # filted_complexes = [] + # for complex in complexes: + # if complex in general_folders: + # print('Complex {} in general folder'.format(complex)) + # continue + # if complex in refined_folders: + # print('Complex {} in refine folder'.format(complex)) + # continue + # if complex in core_folders: + # print('Complex {} in core folder'.format(complex)) + # continue + # filted_complexes.append(complex) + # complexes = filted_complexes + # print(len(complexes)) + + pafnucy_affinity = pd.read_csv('./pdbbind/affinity_data.csv', names = ['pdbid', 'affinity']) + deepatom_affinity = pd.read_csv('./pdbbind/Supplement_INDEX_Benchmark.csv', header = None, names = ['pdbid', 'affinity']) + + affinity_dic = {} + for complex in complexes: + if complex in general_dic: + affinity_dic[complex] = general_dic[complex] + if complex in pafnucy_affinity['pdbid'].unique(): + affinity_dic[complex] = pafnucy_affinity.loc[pafnucy_affinity['pdbid'] == complex, 'affinity'] + if complex in deepatom_affinity['pdbid'].unique(): + affinity_dic[complex] = deepatom_affinity.loc[deepatom_affinity['pdbid'] == complex, 'affinity'] + + print(len(affinity_dic)) + + +if __name__ == "__main__": + root_folder = '/media/data/data_share/pdbbind/' + refined_set = 'refined_set_v2018' + + # root_folder = '/scratch/qil15006/pdbbind/' + # refined_set = 'refined-set' + mode = 'reg' + + general_file = root_folder + 'v2018-other-PL/index/INDEX_general_PL_data.2018' + view_num = 24 + general_folders = os.listdir(root_folder + 'v2018-other-PL') + refined_folders = os.listdir(root_folder + refined_set) + core_folders = os.listdir(root_folder + 'CASF-2016/coreset') + + feature_need_sdf = ['3qlb', '4oem', '4oel', '2fov', '2fou', '2foy'] + + + general_folders = filter(general_folders, feature_need_sdf) + refined_folders = filter(refined_folders, feature_need_sdf) + core_folders = filter(core_folders, feature_need_sdf) + + print(len(general_folders), len(refined_folders), len(core_folders)) + print('Check general and core') + general_folders = check_duplicate(general_folders, core_folders) + print('Check general and refine') + general_folders = check_duplicate(general_folders, refined_folders) + print('Check refine and core') + refined_folders = check_duplicate(refined_folders, core_folders) + print(len(general_folders), len(refined_folders), len(core_folders)) + + core_id_list = core_folders #get_core_id(core_path) + general_dic = read_affinity(general_file, mode = mode) + print(len(general_dic)) + + # check_dic_id(general_dic, general_folders, refined_folders, core_folders) + + # write_octree_list(general_dic, core_folders, refined_folders, depth = 5, mode = mode) + # write_octree_list_refine(general_dic, core_folders, refined_folders, depth=5, mode=mode) + write_points_list(general_dic, core_folders, refined_folders, mode=mode, density = 5) + # write_points_list_refine(general_dic, core_folders, refined_folders, mode=mode, density = 5) + + + +# print(np.mean(affinities)) +# print(np.std(affinities)) +# import matplotlib.pyplot as plt +# import numpy as np +# n, bins, patches = plt.hist(affinities, 30, facecolor='blue', alpha=0.5) +# plt.savefig('histagram.png') diff --git a/pdbbind/java/AdaptiveSurface.java b/pdbbind/java/AdaptiveSurface.java new file mode 100644 index 0000000..ef09184 --- /dev/null +++ b/pdbbind/java/AdaptiveSurface.java @@ -0,0 +1,320 @@ +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.vecmath.Point3d; + +import org.openscience.cdk.ChemFile; +import org.openscience.cdk.geometry.surface.AdaptiveNumericalSurface; +import org.openscience.cdk.geometry.surface.NumericalSurface; +import org.openscience.cdk.geometry.surface.Point_Type; +import org.openscience.cdk.interfaces.IAtomContainer; +import org.openscience.cdk.interfaces.IChemObjectBuilder; +import org.openscience.cdk.io.Mol2Reader; +import org.openscience.cdk.io.PDBReader; +import org.openscience.cdk.io.iterator.IteratingSDFReader; +import org.openscience.cdk.silent.SilentChemObjectBuilder; +import org.openscience.cdk.io.MDLV2000Reader; +import org.openscience.cdk.tools.manipulator.ChemFileManipulator; + +public class AdaptiveSurface { + //public javax.vecmath.Point3d[] getAllSurfacePoints(); + + public static String get_id(String filename) { + String[] parts = filename.split("."); + String part1 = parts[0]; + String[] parts1 = part1.split("_"); + String id = parts1[0]; + return id; + } + + public static void handle_single_mol2(String filename, String path, String id, int tess_level, String tess_type, boolean append) { + //String id = get_id(filename); +// String[] parts1 = filename.split("_"); +// String id = parts1[0]; +// System.out.println("Test"); + //return id; + + try { + File file = new File(path + "/" + id + "/" + filename); + Mol2Reader reader = new Mol2Reader(new FileInputStream(file)); + ChemFile crambin = reader.read(new ChemFile()); + // ChemFile crambin = reader.read(new ChemFile()); generate warning: org.openscience.cdk.config.atomtypes.AtomTypeHandle WARN: Unrecognized hybridization in config file: tetrahedral + // refer to https://github.com/johnmay/cdk/blob/master/base/core/src/main/java/org/openscience/cdk/config/atomtypes/AtomTypeHandler.java + // Another warning: org.openscience.cdk.io.Mol2Reader WARN: Not reading molecule qualifiers + // refer to https://github.com/cdk/cdk/blob/master/storage/io/src/main/java/org/openscience/cdk/io/Mol2Reader.java + List containers= ChemFileManipulator.getAllAtomContainers(crambin); + String write_filename = path + "/" + id + "/" + id + "_cdk_ligand.xyz"; + String atom_filename = path + "/" + id + "/" + id + "_cdk_ligand.txt"; + //FileOutputStream outputStream = new FileOutputStream(write_filename); + BufferedWriter writer = new BufferedWriter(new FileWriter(write_filename, append)); + BufferedWriter writer1 = new BufferedWriter(new FileWriter(atom_filename, append)); + System.out.println("container size: " + containers.size()); + for (int i = 0; i< containers.size(); i++) { + IAtomContainer container = containers.get(i); + String description = container.toString(); + + AdaptiveNumericalSurface new_surface = new AdaptiveNumericalSurface(container, 0, tess_level, tess_type); +// System.out.println(container); +// System.out.println(description); +// System.out.println(new_surface); + try { + //new_surface.calculateSurface(); + //Point3d[] points= new_surface.getAllSurfacePoints(); + ArrayList point_types = new_surface.getAllPointswithAtomType(); + +// System.out.println(points); + + System.out.println(point_types.size()); + for (int j = 0; j < point_types.size(); j++) { + org.openscience.cdk.geometry.surface.Point_Type point_type = point_types.get(j); + Point3d coord = (point_type).getCoord(); + int atom = ((Point_Type) point_type).getAtom(); + String str = coord.x + " " + coord.y + " " + coord.z + "\n"; +// if (j == 1){System.out.println(str);} + writer.write(str); + writer1.write(atom + "\n"); + }} catch (Exception ex) {System.out.println(ex);} + } + /*IChemObject pdb_mol = reader.read();*/ + reader.close(); + + writer.close(); + writer1.close(); + System.out.println("Finished " + write_filename); + } catch (Exception ex) {System.out.println(ex);} + //return container; + } + + public static void handle_single_sdf(String filename, String path, String id, int tess_level, String tess_type, boolean append) { + //String id = get_id(filename); +// String[] parts1 = filename.split("_"); +// String id = parts1[0]; +// System.out.println("Test"); + //return id; + + try { +// File file = new File(path + "/" + id + "/" + filename); + final boolean noStopOnError = true; + final IChemObjectBuilder builder = SilentChemObjectBuilder.getInstance(); + String write_filename = path + "/" + id + "/" + id + "_cdk_ligand.xyz"; + String atom_filename = path + "/" + id + "/" + id + "_cdk_ligand.txt"; + //FileOutputStream outputStream = new FileOutputStream(write_filename); + BufferedWriter writer = new BufferedWriter(new FileWriter(write_filename, append)); + BufferedWriter writer1 = new BufferedWriter(new FileWriter(atom_filename, append)); + +// List containers = new ArrayList(); + + try (InputStream in = new FileInputStream(path + "/" + id + "/" + filename); // .mol also fine! + IteratingSDFReader sdfr = new IteratingSDFReader(in, builder, noStopOnError)) { + while (sdfr.hasNext()) { + IAtomContainer mol = sdfr.next(); +// containers.add(mol); + AdaptiveNumericalSurface new_surface = new AdaptiveNumericalSurface(mol, 0, tess_level, tess_type); + // process molecule here + ArrayList point_types = new_surface.getAllPointswithAtomType(); + System.out.println(point_types.size()); + for (int j = 0; j < point_types.size(); j++) { + org.openscience.cdk.geometry.surface.Point_Type point_type = point_types.get(j); + Point3d coord = (point_type).getCoord(); + int atom = ((Point_Type) point_type).getAtom(); + String str = coord.x + " " + coord.y + " " + coord.z + "\n"; + writer.write(str); + writer1.write(atom + "\n"); + } + } + } catch (IOException e) { + System.err.println("Error: " + e.getMessage()); + } + +// MDLV2000Reader reader = new MDLV2000Reader(new FileInputStream(file)); +// ChemFile crambin = reader.read(new ChemFile()); +// List containers= ChemFileManipulator.getAllAtomContainers(crambin); + writer.close(); + writer1.close(); + System.out.println("Finished " + write_filename); + } catch (Exception ex) {System.out.println(ex);} + //return container; + } + + public static void handle_single_pdb(String filename, String path, String id, int tess_level, String tess_type, String file_type) { + //String id = get_id(filename); +// System.out.println(filename); +// String[] parts = filename.split("_"); + //System.out.println(parts); +// String id = parts[0]; +// System.out.println(id); + + + try { + File file = new File(path + "/" + id + "/" + filename); + PDBReader reader = new PDBReader(new FileInputStream(file)); + //IAtomContainer container = reader.read(SilentChemObjectBuilder.getInstance().newInstance(IAtomContainer.class)); + ChemFile crambin = reader.read(new ChemFile()); + List containers= ChemFileManipulator.getAllAtomContainers(crambin); + String write_filename = path + "/" + id + "/" + id + "_cdk_"+ file_type + ".xyz"; + String atom_filename = path + "/" + id + "/" + id + "_cdk_"+ file_type + ".txt"; + //FileOutputStream outputStream = new FileOutputStream(write_filename); + BufferedWriter writer = new BufferedWriter(new FileWriter(write_filename)); + BufferedWriter writer1 = new BufferedWriter(new FileWriter(atom_filename)); + for (int i = 0; i< containers.size(); i++) { + IAtomContainer container = containers.get(i); +// String description = container.toString(); + + AdaptiveNumericalSurface new_surface = new AdaptiveNumericalSurface(container, 0, tess_level, tess_type); +// System.out.println(container); +// System.out.println(description); +// System.out.println(new_surface); + try { + ArrayList point_types = new_surface.getAllPointswithAtomType(); +// System.out.println(points); + System.out.println(point_types.size()); + for (int j = 0; j < point_types.size(); j++) { + org.openscience.cdk.geometry.surface.Point_Type point_type = point_types.get(j); + Point3d coord = (point_type).getCoord(); + int atom = ((Point_Type) point_type).getAtom(); + String str = coord.x + " " + coord.y + " " + coord.z + "\n"; +// if (j == 1){System.out.println(str);} + writer.write(str); + writer1.write(atom + "\n"); + }} catch (Exception ex) {System.out.println(ex);} + } + /*IChemObject pdb_mol = reader.read();*/ + reader.close(); + + writer.close(); + writer1.close(); + System.out.println("Finished " + write_filename); + } catch (Exception ex) {System.out.println(ex);} + //return container; + } + + public static void handle_single_complex(String filename, String path, String id, int tess_level, String tess_type, String file_type) { + // need revise: to separate ligand/protein. + try { + File file = new File(path + "/" + id + "/" + filename); + PDBReader reader = new PDBReader(new FileInputStream(file)); + ChemFile crambin = reader.read(new ChemFile()); + List containers= ChemFileManipulator.getAllAtomContainers(crambin); + String write_filename = path + "/" + id + "/" + id + "_cdk_"+ file_type + ".xyz"; + String atom_filename = path + "/" + id + "/" + id + "_cdk_"+ file_type + ".txt"; + BufferedWriter writer = new BufferedWriter(new FileWriter(write_filename)); + BufferedWriter writer1 = new BufferedWriter(new FileWriter(atom_filename)); + for (int i = 0; i< containers.size(); i++) { + IAtomContainer container = containers.get(i); + AdaptiveNumericalSurface new_surface = new AdaptiveNumericalSurface(container, 0, tess_level, tess_type); + try { + ArrayList point_types = new_surface.getAllPointswithAtomType(); + System.out.println(point_types.size()); + for (int j = 0; j < point_types.size(); j++) { + org.openscience.cdk.geometry.surface.Point_Type point_type = point_types.get(j); + Point3d coord = (point_type).getCoord(); + int atom = ((Point_Type) point_type).getAtom(); + String str = coord.x + " " + coord.y + " " + coord.z + "\n"; + writer.write(str); + writer1.write(atom + "\n"); + }} catch (Exception ex) {System.out.println(ex);} + } + reader.close(); + + writer.close(); + writer1.close(); + System.out.println("Finished " + write_filename); + } catch (Exception ex) {System.out.println(ex);} + } + + public static void handle_single_id(File complex_id, String path, int tess_level, String tess_type, Boolean protein_flag, String source) { + //File folder = new File("your/path"); + String id_string = complex_id.getName(); + File[] listOfFiles = complex_id.listFiles(); + + if (source.equals("pdbbind")) { + boolean append = false; + for (int i = 0; i < listOfFiles.length; i++) { + String file_name = listOfFiles[i].getName(); + + if (file_name.endsWith(".mol2")){ + System.out.println("Start work on: " + file_name); +// handle_single_mol2(file_name, path, id_string, tess_level, tess_type, append); + System.out.println("Finished: " + file_name); + append = false; + } +// else if (file_name.endsWith("_ligand.sdf")) { +// System.out.println("Start work on: " + file_name); +// handle_single_sdf(file_name, path, id_string, tess_level, tess_type, append); +// System.out.println("Finished: " + file_name); +// append = false; +// } + else if(file_name.endsWith("pocket.pdb")) { + System.out.println("Start work on: " + file_name); + handle_single_pdb(file_name, path, id_string, tess_level, tess_type, "pocket"); + System.out.println("Finished: " + file_name); + } + else if(protein_flag && file_name.endsWith("protein.pdb")) { + System.out.println("Start work on: " + file_name); + handle_single_pdb(file_name, path, id_string, tess_level-2, tess_type, "protein"); + System.out.println("Finished: " + file_name); + } + } // end for + + } // end if source + else if (source.equals("pdbbank")) { + boolean append = false; + for (int i = 0; i < listOfFiles.length; i++) { + String file_name = listOfFiles[i].getName(); + if(file_name.endsWith("_withHs.pdb")) { + handle_single_complex(file_name, path, id_string, tess_level-2, tess_type, "protein"); + } + }// end for + } + else { + System.out.println("This File Source is not supported: " + source); + } + + } + + public static void main(String[] args) { + String path = args[0]; + int tess_level = Integer.parseInt(args[1]); + String tess_type = args[2]; + Boolean protein_flag = Boolean.parseBoolean(args[3]); + String source = args[4]; + //System.out.println(path); + File folder = new File(path); + File[] listOfIDs = folder.listFiles(); + //System.out.println(listOfIDs); + try { + for (int i = 0; i < listOfIDs.length; i++) { + if (listOfIDs[i].isFile()) { + continue; + } + else if (listOfIDs[i].isDirectory()) { + File complex_id = listOfIDs[i]; + String id_string = complex_id.getName(); + System.out.println(id_string); + List already = Arrays.asList("1a1e", "1a4r", + "2pjt", "2phb", "1a4k", "3avj", "1x7a"); + List skip = Arrays.asList("4g17", "3v2w", + "2y80", "4prg", "2phb", "1a1e", "1a4r", + "2pjt", "1a4k"); + +// List already = Arrays.asList(); +// List skip = Arrays.asList(); + if(already.contains(id_string)|| skip.contains(id_string)) { + System.out.println("Skip " + id_string + "."); + continue; + } + handle_single_id(complex_id, path, tess_level, tess_type, protein_flag, source); + System.out.println("\n"); + } + } + } catch (Exception ex) {System.out.println(ex);} + System.out.println("All Finish"); + } +} diff --git a/pdbbind/java/Surface_for_single.class b/pdbbind/java/Surface_for_single.class new file mode 100644 index 0000000..7660871 Binary files /dev/null and b/pdbbind/java/Surface_for_single.class differ diff --git a/pdbbind/java/Surface_for_single.java b/pdbbind/java/Surface_for_single.java new file mode 100644 index 0000000..c3906dc --- /dev/null +++ b/pdbbind/java/Surface_for_single.java @@ -0,0 +1,302 @@ +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.nio.file.Path; +import java.nio.file.Paths; + + +import javax.vecmath.Point3d; + +import org.openscience.cdk.ChemFile; +import org.openscience.cdk.geometry.surface.AdaptiveNumericalSurface; +import org.openscience.cdk.geometry.surface.NumericalSurface; +import org.openscience.cdk.geometry.surface.Point_Type; +import org.openscience.cdk.interfaces.IAtomContainer; +import org.openscience.cdk.interfaces.IChemObjectBuilder; +import org.openscience.cdk.io.Mol2Reader; +import org.openscience.cdk.io.PDBReader; +import org.openscience.cdk.io.iterator.IteratingSDFReader; +import org.openscience.cdk.silent.SilentChemObjectBuilder; +import org.openscience.cdk.io.MDLV2000Reader; +import org.openscience.cdk.tools.manipulator.ChemFileManipulator; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.vecmath.Point3d; + +import org.openscience.cdk.ChemFile; +import org.openscience.cdk.geometry.surface.AdaptiveNumericalSurface; +import org.openscience.cdk.geometry.surface.NumericalSurface; +import org.openscience.cdk.geometry.surface.Point_Type; +import org.openscience.cdk.interfaces.IAtomContainer; +import org.openscience.cdk.interfaces.IChemObjectBuilder; +import org.openscience.cdk.io.Mol2Reader; +import org.openscience.cdk.io.PDBReader; +import org.openscience.cdk.io.iterator.IteratingSDFReader; +import org.openscience.cdk.silent.SilentChemObjectBuilder; +import org.openscience.cdk.io.MDLV2000Reader; +import org.openscience.cdk.tools.manipulator.ChemFileManipulator; + +public class Surface_for_single { + //public javax.vecmath.Point3d[] getAllSurfacePoints(); + + public static String get_id(String filename) { + String[] parts = filename.split("."); + String part1 = parts[0]; + String[] parts1 = part1.split("_"); + String id = parts1[0]; + return id; + } + + public static void handle_single_mol2(String filename, String path, String id, int tess_level, String tess_type, String file_type, boolean append) { + + try { + File file = new File(path + "/" + id + "/" + filename); + Mol2Reader reader = new Mol2Reader(new FileInputStream(file)); + ChemFile crambin = reader.read(new ChemFile()); + // ChemFile crambin = reader.read(new ChemFile()); generate warning: org.openscience.cdk.config.atomtypes.AtomTypeHandle WARN: Unrecognized hybridization in config file: tetrahedral + // refer to https://github.com/johnmay/cdk/blob/master/base/core/src/main/java/org/openscience/cdk/config/atomtypes/AtomTypeHandler.java + // Another warning: org.openscience.cdk.io.Mol2Reader WARN: Not reading molecule qualifiers + // refer to https://github.com/cdk/cdk/blob/master/storage/io/src/main/java/org/openscience/cdk/io/Mol2Reader.java + List containers= ChemFileManipulator.getAllAtomContainers(crambin); + String write_filename = path + "/" + id + "/" + id + "_cdk_"+ file_type + ".xyz"; + String atom_filename = path + "/" + id + "/" + id + "_cdk_"+ file_type + ".txt"; + //FileOutputStream outputStream = new FileOutputStream(write_filename); + BufferedWriter writer = new BufferedWriter(new FileWriter(write_filename, append)); + BufferedWriter writer1 = new BufferedWriter(new FileWriter(atom_filename, append)); + System.out.println("container size: " + containers.size()); + for (int i = 0; i< containers.size(); i++) { + IAtomContainer container = containers.get(i); +// String description = container.toString(); + +// AdaptiveNumericalSurface new_surface = new AdaptiveNumericalSurface(container, 0, tess_level, tess_type); + NumericalSurface new_surface = new NumericalSurface(container, 0, tess_level, tess_type); +// System.out.println(container); +// System.out.println(description); +// System.out.println(new_surface); + try { + //new_surface.calculateSurface(); + //Point3d[] points= new_surface.getAllSurfacePoints(); + ArrayList point_types = new_surface.getAllPointswithAtomType(); + +// System.out.println(points); + + System.out.println(point_types.size()); + for (int j = 0; j < point_types.size(); j++) { + org.openscience.cdk.geometry.surface.Point_Type point_type = point_types.get(j); + Point3d coord = (point_type).getCoord(); + int atom = ((Point_Type) point_type).getAtom(); + int atom_index = ((Point_Type) point_type).getIndex(); + String str = coord.x + " " + coord.y + " " + coord.z + "\n"; +// if (j == 1){System.out.println(str);} + writer.write(str); +// writer1.write(atom + "\n"); + writer1.write(atom + " " + atom_index + "\n"); + }} catch (Exception ex) {System.out.println("remind QQ Error: " + ex);} + } + /*IChemObject pdb_mol = reader.read();*/ + reader.close(); + + writer.close(); + writer1.close(); + System.out.println("Finished " + write_filename); + } catch (Exception ex) {System.out.println(ex);} + //return container; + } + + public static void handle_single_pdb(String filename, String path, String id, int tess_level, String tess_type, String file_type) { + //String id = get_id(filename); +// System.out.println(filename); +// String[] parts = filename.split("_"); + //System.out.println(parts); +// String id = parts[0]; +// System.out.println(id); + + + try { + File file = new File(path + "/" + id + "/" + filename); + PDBReader reader = new PDBReader(new FileInputStream(file)); + //IAtomContainer container = reader.read(SilentChemObjectBuilder.getInstance().newInstance(IAtomContainer.class)); + ChemFile crambin = reader.read(new ChemFile()); + List containers= ChemFileManipulator.getAllAtomContainers(crambin); + String write_filename = path + "/" + id + "/" + id + "_cdk_"+ file_type + ".xyz"; + String atom_filename = path + "/" + id + "/" + id + "_cdk_"+ file_type + ".txt"; + //FileOutputStream outputStream = new FileOutputStream(write_filename); + BufferedWriter writer = new BufferedWriter(new FileWriter(write_filename)); + BufferedWriter writer1 = new BufferedWriter(new FileWriter(atom_filename)); + for (int i = 0; i< containers.size(); i++) { + IAtomContainer container = containers.get(i); +// String description = container.toString(); + System.out.println(containers.size()); + +// AdaptiveNumericalSurface new_surface = new AdaptiveNumericalSurface(container, 0, tess_level, tess_type); + NumericalSurface new_surface = new NumericalSurface(container, 0, tess_level, tess_type); +// System.out.println(container); +// System.out.println(description); +// System.out.println(new_surface); + try { + ArrayList point_types = new_surface.getAllPointswithAtomType(); +// System.out.println(points); + System.out.println(point_types.size()); + for (int j = 0; j < point_types.size(); j++) { + org.openscience.cdk.geometry.surface.Point_Type point_type = point_types.get(j); + Point3d coord = (point_type).getCoord(); + int atom = ((Point_Type) point_type).getAtom(); + int atom_index = ((Point_Type) point_type).getIndex(); + String str = coord.x + " " + coord.y + " " + coord.z + "\n"; +// if (j == 1){System.out.println(str);} + writer.write(str); +// writer1.write(atom + "\n"); + writer1.write(atom + " " + atom_index + "\n"); + }} catch (Exception ex) {System.out.println(ex);} + } + /*IChemObject pdb_mol = reader.read();*/ + reader.close(); + + writer.close(); + writer1.close(); + System.out.println("Finished " + write_filename); + } catch (Exception ex) {System.out.println(ex);} + //return container; + } + + public static void handle_single_complex(String filename, String path, String id, int tess_level, String tess_type, String file_type) { + // need revise: to separate ligand/protein. + try { + File file = new File(path + "/" + id + "/" + filename); + PDBReader reader = new PDBReader(new FileInputStream(file)); + ChemFile crambin = reader.read(new ChemFile()); + List containers= ChemFileManipulator.getAllAtomContainers(crambin); + String write_filename = path + "/" + id + "/" + id + "_cdk_"+ file_type + ".xyz"; + String atom_filename = path + "/" + id + "/" + id + "_cdk_"+ file_type + ".txt"; + BufferedWriter writer = new BufferedWriter(new FileWriter(write_filename)); + BufferedWriter writer1 = new BufferedWriter(new FileWriter(atom_filename)); + for (int i = 0; i< containers.size(); i++) { + IAtomContainer container = containers.get(i); + AdaptiveNumericalSurface new_surface = new AdaptiveNumericalSurface(container, 0, tess_level, tess_type); + try { + ArrayList point_types = new_surface.getAllPointswithAtomType(); + System.out.println(point_types.size()); + for (int j = 0; j < point_types.size(); j++) { + org.openscience.cdk.geometry.surface.Point_Type point_type = point_types.get(j); + Point3d coord = (point_type).getCoord(); + int atom = ((Point_Type) point_type).getAtom(); + int atom_index = ((Point_Type) point_type).getIndex(); + String str = coord.x + " " + coord.y + " " + coord.z + "\n"; + writer.write(str); +// writer1.write(atom + "\n"); + writer1.write(atom + " " + atom_index + "\n"); + }} catch (Exception ex) {System.out.println(ex);} + } + reader.close(); + + writer.close(); + writer1.close(); + System.out.println("Finished " + write_filename); + } catch (Exception ex) {System.out.println(ex);} + } + + public static void handle_single_id(File complex_id, String path, int tess_level, String tess_type, Boolean protein_flag, String source) { + //File folder = new File("your/path"); + String id_string = complex_id.getName(); + File[] listOfFiles = complex_id.listFiles(); + + if (source.equals("pdbbind")) { + boolean append = false; + for (int i = 0; i < listOfFiles.length; i++) { + String file_name = listOfFiles[i].getName(); + + if (file_name.endsWith("ligand.mol2")){ + System.out.println("Start work on: " + file_name); + handle_single_mol2(file_name, path, id_string, tess_level, tess_type, "ligand", append); + System.out.println("Finished: " + file_name); + append = false; + } + else if(file_name.endsWith("pocket.pdb")) { + System.out.println("Start work on: " + file_name); + handle_single_pdb(file_name, path, id_string, tess_level, tess_type, "pocket"); + System.out.println("Finished: " + file_name); + } + else if(protein_flag && file_name.endsWith("protein.pdb")) { + System.out.println("Start work on: " + file_name); + handle_single_pdb(file_name, path, id_string, tess_level-2, tess_type, "protein"); + System.out.println("Finished: " + file_name); + } + } // end for + + } // end if source + else if (source.equals("pdbbank")) { + boolean append = false; + for (int i = 0; i < listOfFiles.length; i++) { + String file_name = listOfFiles[i].getName(); + if(file_name.endsWith("_withHs.pdb")) { + handle_single_complex(file_name, path, id_string, tess_level-2, tess_type, "protein"); + } + }// end for + } + else if (source.equals("astex")) { + boolean append = false; + for (int i = 0; i < listOfFiles.length; i++) { + String file_name = listOfFiles[i].getName(); + + if (file_name.endsWith("ligand.mol2")){ + System.out.println("Start work on: " + file_name); + handle_single_mol2(file_name, path, id_string, tess_level, tess_type, "ligand", append); + System.out.println("Finished: " + file_name); + append = false; + } + else if(file_name.endsWith("protein.mol2")) { + System.out.println("Start work on: " + file_name); + handle_single_mol2(file_name, path, id_string, tess_level, tess_type, "pocket", append); + System.out.println("Finished: " + file_name); + } + } // end for + + } + + else { + System.out.println("This File Source is not supported: " + source); + } + + } + public static void main(String[] args) { + + int tess_level = Integer.parseInt(args[0]); + String tess_type = args[1]; + String source = args[2]; + + Path currentRelativePath = Paths.get(""); + String s = currentRelativePath.toAbsolutePath().toString(); + String[] array = s.split("/"); + +// String complex_id = array[array.length-1]; + array = Arrays.copyOf(array, array.length - 1); + String dataset_path = String.join("/", array); + + File complex_file = new File(s); + + if (complex_file.isDirectory()) { + String id_string = complex_file.getName(); + handle_single_id(complex_file, dataset_path, tess_level, tess_type, false, source); + } + else { + System.out.println(s + "is not a folder"); + } + + } +} + diff --git a/pdbbind/java/Surface_from_Mol2.java b/pdbbind/java/Surface_from_Mol2.java new file mode 100644 index 0000000..9bdcf4a --- /dev/null +++ b/pdbbind/java/Surface_from_Mol2.java @@ -0,0 +1,177 @@ +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.util.List; + +import javax.vecmath.Point3d; + +import org.openscience.cdk.geometry.surface.NumericalSurface; +import org.openscience.cdk.interfaces.IAtomContainer; +import org.openscience.cdk.io.*; +import org.openscience.cdk.silent.SilentChemObjectBuilder; +import org.openscience.cdk.tools.manipulator.ChemFileManipulator; +import org.apache.commons.io.FileUtils; +import org.openscience.cdk.*; + + +public class Surface_from_Mol2 { + + //public javax.vecmath.Point3d[] getAllSurfacePoints(); + + public static String get_id(String filename) { + String[] parts = filename.split("."); + String part1 = parts[0]; + String[] parts1 = part1.split("_"); + String id = parts1[0]; + return id; + } + + public static void handle_single_mol2(String filename, String path) { + //String id = get_id(filename); + String[] parts1 = filename.split("_"); + String id = parts1[0]; + System.out.println("Test"); + //return id; + + try { + File file = new File(path + "/" + id + "/" + filename); + Mol2Reader reader = new Mol2Reader(new FileInputStream(file)); + //IAtomContainer container = reader.read(SilentChemObjectBuilder.getInstance().newInstance(IAtomContainer.class)); + ChemFile crambin = reader.read(new ChemFile()); + + List containers= ChemFileManipulator.getAllAtomContainers(crambin); + String write_filename = path + "/" + id + "/" + id + "_cdk_ligand.xyz"; + //FileOutputStream outputStream = new FileOutputStream(write_filename); + BufferedWriter writer = new BufferedWriter(new FileWriter(write_filename)); + for (int i = 0; i< containers.size(); i++) { + IAtomContainer container = containers.get(i); + String description = container.toString(); + + NumericalSurface new_surface = new NumericalSurface(container, 0, 6); + System.out.println(container); + System.out.println(description); + System.out.println(new_surface); + // Next line is wrong. + try { + new_surface.calculateSurface(); + Point3d[] points= new_surface.getAllSurfacePoints(); +// System.out.println(points); + System.out.println(points.length); + for (int j = 0; j < points.length; j++) { + String str = points[j].x + " " + points[j].y + " " + points[j].z + "\n"; + if (j == 1){System.out.println(str);} + writer.write(str); + }} catch (Exception ex) {System.out.println(ex);} + } + /*IChemObject pdb_mol = reader.read();*/ + reader.close(); + + writer.close(); + System.out.println("Finished " + write_filename); + } catch (Exception ex) {System.out.println(ex);} + //return container; + } + + public static void handle_single_pdb(String filename, String path) { + //String id = get_id(filename); + System.out.println(filename); + String[] parts = filename.split("_"); + //System.out.println(parts); + String id = parts[0]; + System.out.println(id); + + try { + File file = new File(path + "/" + id + "/" + filename); + PDBReader reader = new PDBReader(new FileInputStream(file)); + //IAtomContainer container = reader.read(SilentChemObjectBuilder.getInstance().newInstance(IAtomContainer.class)); + ChemFile crambin = reader.read(new ChemFile()); + List containers= ChemFileManipulator.getAllAtomContainers(crambin); + String write_filename = path + "/" + id + "/" + id + "_cdk_pocket.xyz"; + //FileOutputStream outputStream = new FileOutputStream(write_filename); + BufferedWriter writer = new BufferedWriter(new FileWriter(write_filename)); +// writer.write("ply\n" + +// "format ascii 1.0\n" + +// "element vertex 107812\n" + +// "property float x\n" + +// "property float y\n" + +// "property float z\n" + +// "property float nx\n" + +// "property float ny\n" + +// "property float nz\n" + +// "element face 0\n" + +// "property list uchar int vertex_indices\n" + +// "end_header"); + for (int i = 0; i< containers.size(); i++) { + IAtomContainer container = containers.get(i); + String description = container.toString(); + + NumericalSurface new_surface = new NumericalSurface(container, 0, 6); + System.out.println(container); + System.out.println(description); + System.out.println(new_surface); + // Next line is wrong. + try { + new_surface.calculateSurface(); + Point3d[] points= new_surface.getAllSurfacePoints(); +// System.out.println(points); + System.out.println(points.length); + for (int j = 0; j < points.length; j++) { + String str = points[j].x + " " + points[j].y + " " + points[j].z + "\n"; + if (j == 1){System.out.println(str);} + writer.write(str); + }} catch (Exception ex) {System.out.println(ex);} + } + /*IChemObject pdb_mol = reader.read();*/ + reader.close(); + + writer.close(); + System.out.println("Finished " + write_filename); + } catch (Exception ex) {System.out.println(ex);} + //return container; + + } + + public static void handle_single_id(File complex_id, String path) { + //File folder = new File("your/path"); + File[] listOfFiles = complex_id.listFiles(); + for (int i = 0; i < listOfFiles.length; i++) { + String file_name = listOfFiles[i].getName(); + + if (file_name.endsWith(".mol2")){ + System.out.println("Start work on: " + file_name); + handle_single_mol2(file_name, path); + System.out.println("Finished: " + file_name); + + } + else if(file_name.endsWith("pocket.pdb")) { + System.out.println("Start work on: " + file_name); + handle_single_pdb(file_name, path); + System.out.println("Finished: " + file_name); + } + } + } + + public static void main(String[] args) { + String path = args[0]; + //System.out.println(path); + File folder = new File(path); + File[] listOfIDs = folder.listFiles(); + //System.out.println(listOfIDs); + try { + for (int i = 0; i < listOfIDs.length; i++) { + if (listOfIDs[i].isFile()) { + //System.out.println("File " + listOfFiles[i].getName()); + continue; + } + else if (listOfIDs[i].isDirectory()) { + File complex_id = listOfIDs[i]; + //String str = complex_id.getPath(); + //System.out.println(str); + handle_single_id(complex_id, path); + } + } + } catch (Exception ex) {System.out.println(ex);} + } +} diff --git a/pdbbind/java/Surface_with_Type.class b/pdbbind/java/Surface_with_Type.class new file mode 100644 index 0000000..a4bdb7f Binary files /dev/null and b/pdbbind/java/Surface_with_Type.class differ diff --git a/pdbbind/java/Surface_with_Type.java b/pdbbind/java/Surface_with_Type.java new file mode 100644 index 0000000..6d43b80 --- /dev/null +++ b/pdbbind/java/Surface_with_Type.java @@ -0,0 +1,259 @@ +// The updated version for Adaptive surface. +// Reduced the number of points that would be generated. +// Also generate txt file to describe the points. e.g. atom type, index. + + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.vecmath.Point3d; + +import org.openscience.cdk.ChemFile; +import org.openscience.cdk.geometry.surface.NumericalSurface; +import org.openscience.cdk.geometry.surface.Point_Type; +import org.openscience.cdk.interfaces.IAtomContainer; +import org.openscience.cdk.io.Mol2Reader; +import org.openscience.cdk.io.PDBReader; +import org.openscience.cdk.tools.manipulator.ChemFileManipulator; + +public class Surface_with_Type { + //public javax.vecmath.Point3d[] getAllSurfacePoints(); + + public static String get_id(String filename) { + String[] parts = filename.split("."); + String part1 = parts[0]; + String[] parts1 = part1.split("_"); + String id = parts1[0]; + return id; + } + + public static void handle_single_mol2(String filename, String path) { + //String id = get_id(filename); + String[] parts1 = filename.split("_"); + String id = parts1[0]; +// System.out.println("Test"); + //return id; + + try { + File file = new File(path + "/" + id + "/" + filename); + Mol2Reader reader = new Mol2Reader(new FileInputStream(file)); + //IAtomContainer container = reader.read(SilentChemObjectBuilder.getInstance().newInstance(IAtomContainer.class)); + ChemFile crambin = reader.read(new ChemFile()); + + List containers= ChemFileManipulator.getAllAtomContainers(crambin); + String write_filename = path + "/" + id + "/" + id + "_cdk_ligand.xyz"; + String atom_filename = path + "/" + id + "/" + id + "_cdk_ligand.txt"; + //FileOutputStream outputStream = new FileOutputStream(write_filename); + BufferedWriter writer = new BufferedWriter(new FileWriter(write_filename)); + BufferedWriter writer1 = new BufferedWriter(new FileWriter(atom_filename)); + for (int i = 0; i< containers.size(); i++) { + IAtomContainer container = containers.get(i); +// String description = container.toString(); + + NumericalSurface new_surface = new NumericalSurface(container, 0, 6); +// System.out.println(container); +// System.out.println(description); +// System.out.println(new_surface); + // Next line is wrong. + try { + //new_surface.calculateSurface(); + //Point3d[] points= new_surface.getAllSurfacePoints(); + ArrayList point_types = new_surface.getAllPointswithAtomType(); + +// System.out.println(points); + + System.out.println(point_types.size()); + for (int j = 0; j < point_types.size(); j++) { + org.openscience.cdk.geometry.surface.Point_Type point_type = point_types.get(j); + Point3d coord = (point_type).getCoord(); + int atom = ((Point_Type) point_type).getAtom(); + int atom_index = ((Point_Type) point_type).getIndex(); + String str = coord.x + " " + coord.y + " " + coord.z + "\n"; + if (j == 1){System.out.println(str);} + writer.write(str); + writer1.write(atom + " " + atom_index + "\n"); + }} catch (Exception ex) {System.out.println(ex);} + } + /*IChemObject pdb_mol = reader.read();*/ + reader.close(); + + writer.close(); + writer1.close(); + System.out.println("Finished " + write_filename); + } catch (Exception ex) {System.out.println(ex);} + //return container; + } + + public static void handle_single_pdb(String filename, String path) { + //String id = get_id(filename); +// System.out.println(filename); + String[] parts = filename.split("_"); + //System.out.println(parts); + String id = parts[0]; +// System.out.println(id); + + try { + File file = new File(path + "/" + id + "/" + filename); + PDBReader reader = new PDBReader(new FileInputStream(file)); + //IAtomContainer container = reader.read(SilentChemObjectBuilder.getInstance().newInstance(IAtomContainer.class)); + ChemFile crambin = reader.read(new ChemFile()); + List containers= ChemFileManipulator.getAllAtomContainers(crambin); + String write_filename = path + "/" + id + "/" + id + "_cdk_pocket.xyz"; + String atom_filename = path + "/" + id + "/" + id + "_cdk_pocket.txt"; + //FileOutputStream outputStream = new FileOutputStream(write_filename); + BufferedWriter writer = new BufferedWriter(new FileWriter(write_filename)); + BufferedWriter writer1 = new BufferedWriter(new FileWriter(atom_filename)); + for (int i = 0; i< containers.size(); i++) { + IAtomContainer container = containers.get(i); +// String description = container.toString(); + + NumericalSurface new_surface = new NumericalSurface(container, 0, 6); +// System.out.println(container); +// System.out.println(description); +// System.out.println(new_surface); + // Next line is wrong. + try { + ArrayList point_types = new_surface.getAllPointswithAtomType(); +// System.out.println(points); + System.out.println(point_types.size()); + for (int j = 0; j < point_types.size(); j++) { + org.openscience.cdk.geometry.surface.Point_Type point_type = point_types.get(j); + Point3d coord = (point_type).getCoord(); + int atom = ((Point_Type) point_type).getAtom(); + int index = ((Point_Type) point_type).getIndex(); + String str = coord.x + " " + coord.y + " " + coord.z + "\n"; + if (j == 1){System.out.println(str);} + writer.write(str); + writer1.write(atom + " " + index + "\n"); + }} catch (Exception ex) {System.out.println(ex);} + } + /*IChemObject pdb_mol = reader.read();*/ + reader.close(); + + writer.close(); + writer1.close(); + System.out.println("Finished " + write_filename); + } catch (Exception ex) {System.out.println(ex);} + //return container; + + } + + public static void handle_single_protein(String filename, String path) { + //String id = get_id(filename); +// System.out.println(filename); + String[] parts = filename.split("_"); + //System.out.println(parts); + String id = parts[0]; +// System.out.println(id); + + try { + File file = new File(path + "/" + id + "/" + filename); + PDBReader reader = new PDBReader(new FileInputStream(file)); + //IAtomContainer container = reader.read(SilentChemObjectBuilder.getInstance().newInstance(IAtomContainer.class)); + ChemFile crambin = reader.read(new ChemFile()); + List containers= ChemFileManipulator.getAllAtomContainers(crambin); + String write_filename = path + "/" + id + "/" + id + "_cdk_protein.xyz"; + String atom_filename = path + "/" + id + "/" + id + "_cdk_protein.txt"; + //FileOutputStream outputStream = new FileOutputStream(write_filename); + BufferedWriter writer = new BufferedWriter(new FileWriter(write_filename)); + BufferedWriter writer1 = new BufferedWriter(new FileWriter(atom_filename)); + for (int i = 0; i< containers.size(); i++) { + IAtomContainer container = containers.get(i); +// String description = container.toString(); + + NumericalSurface new_surface = new NumericalSurface(container, 0, 6); +// System.out.println(container); +// System.out.println(description); +// System.out.println(new_surface); + // Next line is wrong. + try { + ArrayList point_types = new_surface.getAllPointswithAtomType(); +// System.out.println(points); + System.out.println(point_types.size()); + for (int j = 0; j < point_types.size(); j++) { + org.openscience.cdk.geometry.surface.Point_Type point_type = point_types.get(j); + Point3d coord = (point_type).getCoord(); + int atom = ((Point_Type) point_type).getAtom(); + int index = ((Point_Type) point_type).getIndex(); + String str = coord.x + " " + coord.y + " " + coord.z + "\n"; + if (j == 1){System.out.println(str);} + writer.write(str); + writer1.write(atom + " " + index + "\n"); + }} catch (Exception ex) {System.out.println(ex);} + } + /*IChemObject pdb_mol = reader.read();*/ + reader.close(); + + writer.close(); + writer1.close(); + System.out.println("Finished " + write_filename); + } catch (Exception ex) {System.out.println(ex);} + //return container; + + } + + public static void handle_single_id(File complex_id, String path) { + //File folder = new File("your/path"); + File[] listOfFiles = complex_id.listFiles(); + for (int i = 0; i < listOfFiles.length; i++) { + String file_name = listOfFiles[i].getName(); + + if (file_name.endsWith(".mol2")){ + System.out.println("Start work on: " + file_name); + handle_single_mol2(file_name, path); + System.out.println("Finished: " + file_name); + + } + else if(file_name.endsWith("pocket.pdb")) { + System.out.println("Start work on: " + file_name); + handle_single_pdb(file_name, path); + System.out.println("Finished: " + file_name); + } +// else if(file_name.endsWith("protein.pdb")) { +// System.out.println("Start work on: " + file_name); +// handle_single_protein(file_name, path); +// System.out.println("Finished: " + file_name); +// } + + + } + } + + public static void main(String[] args) { + String path = args[0]; + //System.out.println(path); + File folder = new File(path); + File[] listOfIDs = folder.listFiles(); + //System.out.println(listOfIDs); + try { + for (int i = 0; i < listOfIDs.length; i++) { + if (listOfIDs[i].isFile()) { + continue; + } + else if (listOfIDs[i].isDirectory()) { + File complex_id = listOfIDs[i]; + String id_string = complex_id.getName(); + System.out.println(id_string); +// List already = Arrays.asList("1a1e", "1a4r", +// "2pjt", "2phb", "1a4k", "3avj", "1x7a", +// "4tvj", "4crf", "4PJT", "1i1e", "3huc"); +// List skip = Arrays.asList("4g17", "3v2w", +// "2y80", "4prg", "2hmu", "4m0f"); + List already = Arrays.asList(); + List skip = Arrays.asList(); + if(already.contains(id_string)|| skip.contains(id_string)) { + System.out.println("Skip " + id_string + "."); + continue; + } + handle_single_id(complex_id, path); + } + } + } catch (Exception ex) {System.out.println(ex);} + System.out.println("All Finish"); + } +} diff --git a/pdbbind/java/Tess_Val.java b/pdbbind/java/Tess_Val.java new file mode 100644 index 0000000..b4640ae --- /dev/null +++ b/pdbbind/java/Tess_Val.java @@ -0,0 +1,18 @@ +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import javax.vecmath.Point3d; + +import org.openscience.cdk.geometry.surface.Tessellate; + +public class Tess_Val { + public static void main(String[] args) { + Tessellate tess = new Tessellate("ico", 4); + tess.doTessellate(); +// System.out.println(tess.getNumberOfTriangles()); + Point3d[] point_list = tess.getTessAsPoint3ds(); + Set point_set = new HashSet(Arrays.asList(point_list)); + System.out.println(point_set.size()); + } +} diff --git a/pdbbind/java/cdk-2.3-SNAPSHOT.jar b/pdbbind/java/cdk-2.3-SNAPSHOT.jar new file mode 100644 index 0000000..1c01c94 Binary files /dev/null and b/pdbbind/java/cdk-2.3-SNAPSHOT.jar differ diff --git a/pdbbind/write_complex.py b/pdbbind/write_complex.py new file mode 100644 index 0000000..ff5dc06 --- /dev/null +++ b/pdbbind/write_complex.py @@ -0,0 +1,19 @@ +import os + +path = os.getcwd() +complex_id = path.split('/')[-1] + +# id_list_file = path + '/file_list.txt' +# with open(id_list_file, 'w') as fw: +# fw.write(complex_id) + + +# file_list is used to get .points from pdb points. +id_list_file = path + '/file_list.txt' +with open(id_list_file, 'w') as fw: + fw.write(complex_id) + +# point_list is used to get .octrees from .points +id_list_file = path + '/point_list.txt' +with open(id_list_file, 'w') as fw: + fw.write(path + '/' + complex_id + '_points.points') \ No newline at end of file diff --git a/tensorflow/.DS_Store b/tensorflow/.DS_Store new file mode 100644 index 0000000..d6e1183 Binary files /dev/null and b/tensorflow/.DS_Store differ diff --git a/tensorflow/data/cls_modelnet.py b/tensorflow/data/cls_modelnet.py new file mode 100644 index 0000000..c8d6809 --- /dev/null +++ b/tensorflow/data/cls_modelnet.py @@ -0,0 +1,236 @@ +import os +import sys +import argparse + + +parser = argparse.ArgumentParser() +parser.add_argument('--run', type=str, required=True, + help='The command to run.') +parser.add_argument('--converter', type=str, required=False, + default='util/convert_tfrecords.py', + help='The path of the convert_tfrecords') +parser.add_argument('--scanner', type=str, required=False, + help='The path of the virtual_scanner') +parser.add_argument('--octree', type=str, required=False, default='octree', + help='The path of the octree') +parser.add_argument('--simplify_points', type=str, required=False, + default='simplify_points', + help='The path of the simplify_points') + + +abs_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +root_folder = os.path.join(abs_path, 'script/dataset/ModelNet40') + + +args = parser.parse_args() +octree = args.octree +converter = os.path.join(abs_path, args.converter) +virtual_scanner = args.scanner +simplify = args.simplify_points + + +def download_m40(): + # download via wget + if not os.path.exists(root_folder): + os.makedirs(root_folder) + url = 'http://modelnet.cs.princeton.edu/ModelNet40.zip' + cmd = 'wget %s -O %s/ModelNet40.zip' % (url, root_folder) + print(cmd) + os.system(cmd) + + # unzip + cmd = 'unzip %s/ModelNet40.zip -d %s' % (root_folder, root_folder) + print(cmd) + os.system(cmd) + + +def download_m40_points(): + # download via wget + if not os.path.exists(root_folder): + os.makedirs(root_folder) + url = 'https://www.dropbox.com/s/m233s9eza3acj2a/ModelNet40.points.zip?dl=0' + cmd = 'wget %s -O %s/ModelNet40.points.zip' % (url, root_folder) + print(cmd) + os.system(cmd) + + # unzip + cmd = 'unzip %s/ModelNet40.points.zip -d %s/ModelNet40.points' % ( + root_folder, root_folder) + print(cmd) + os.system(cmd) + + +def clean_off_file(filename): + # read the contents of the file + with open(filename) as fid: + file_str = fid.read() + # fix the file + if file_str[0:3] != 'OFF': + print('Error: not an OFF file: ' + filename) + elif file_str[0:4] != 'OFF\n': + print('Info: fix an OFF file: ' + filename) + new_str = file_str[0:3] + '\n' + file_str[3:] + with open(filename, 'w') as f_rewrite: + f_rewrite.write(new_str) + + +def m40_get_filelist(root_folder, train=True, suffix='off'): + filelist, category = [], [] + folders = sorted(os.listdir(root_folder)) + assert(len(folders) == 40) + for idx, folder in enumerate(folders): + subfolder = 'train' if train else 'test' + current_folder = os.path.join(root_folder, folder, subfolder) + filenames = sorted(os.listdir(current_folder)) + for filename in filenames: + if filename.endswith(suffix): + filelist.append(os.path.join(folder, subfolder, filename)) + category.append(idx) + return filelist, category + + +def m40_move_files(src_folder, des_folder, suffix): + folders = os.listdir(src_folder) + for folder in folders: + for subfolder in ['train', 'test']: + curr_src_folder = os.path.join(src_folder, folder, subfolder) + curr_des_folder = os.path.join(des_folder, folder, subfolder) + if not os.path.exists(curr_des_folder): + os.makedirs(curr_des_folder) + filenames = os.listdir(curr_src_folder) + for filename in filenames: + if filename.endswith('.points'): + os.rename(os.path.join(curr_src_folder, filename), + os.path.join(curr_des_folder, filename)) + + +def m40_convert_mesh_to_points(): + mesh_folder = os.path.join(root_folder, 'ModelNet40') + # Delete 3 files since the virtualscanner can not well deal with them + filelist = ['cone/train/cone_0117.off', + 'curtain/train/curtain_0066.off', + 'car/train/car_0021.off.off'] + for filename in filelist: + filename = os.path.join(mesh_folder, filename) + if os.path.exists(filename): + os.remove(filename) + + # clean the off files + train_list, _ = m40_get_filelist(mesh_folder, train=True, suffix='off') + test_list, _ = m40_get_filelist(mesh_folder, train=False, suffix='off') + filelist = train_list + test_list + for filename in filelist: + clean_off_file(os.path.join(mesh_folder, filename)) + + # run virtualscanner + folders = os.listdir(mesh_folder) + for folder in folders: + for subfolder in ['train', 'test']: + curr_folder = os.path.join(mesh_folder, folder, subfolder) + cmd = '%s %s 14' % (virtual_scanner, curr_folder) + print(cmd) + os.system(cmd) + + # move points + m40_move_files(mesh_folder, mesh_folder + '.points', 'points') + + +def m40_convert_points_to_octree(depth=5, adaptive=0, node_dis=0): + points_folder = os.path.join(root_folder, 'ModelNet40.points') + folders = os.listdir(points_folder) + for folder in folders: + for subfolder in ['train', 'test']: + curr_folder = os.path.join(points_folder, folder, subfolder) + # write filelist to disk + filenames = os.listdir(curr_folder) + filelist_name = os.path.join(curr_folder, 'list.txt') + with open(filelist_name, 'w') as fid: + for filename in filenames: + if filename.endswith('.points'): + fid.write(os.path.join(curr_folder, filename) + '\n') + # run octree + octree_folder = points_folder[:-6] + 'octree.%d' % depth + if adaptive == 1: + octree_folder = octree_folder + '.adaptive' + output_path = os.path.join(octree_folder, folder, subfolder) + if not os.path.exists(output_path): + os.makedirs(output_path) + cmd = '%s --filenames %s --output_path %s --depth %d --adaptive %d --node_dis %d --axis z' % \ + (octree, filelist_name, output_path, depth, adaptive, node_dis) + print(cmd) + os.system(cmd) + + +def m40_simplify_points(resolution=64): + # rename and backup the original folders + points_folder = os.path.join(root_folder, 'ModelNet40.points') + original_folder = points_folder + ".dense" + if os.path.exists(points_folder): + os.rename(points_folder, original_folder) + + folders = os.listdir(original_folder) + for folder in folders: + for subfolder in ['train', 'test']: + curr_folder = os.path.join(original_folder, folder, subfolder) + # write filelist to disk + filenames = os.listdir(curr_folder) + filelist_name = os.path.join(curr_folder, 'list.txt') + with open(filelist_name, 'w') as fid: + for filename in filenames: + if filename.endswith('.points'): + fid.write(os.path.join(curr_folder, filename) + '\n') + # run simplify_points + output_path = os.path.join(points_folder, folder, subfolder) + if not os.path.exists(output_path): + os.makedirs(output_path) + cmd = '%s --filenames %s --output_path %s --dim %d' % \ + (simplify, filelist_name, output_path, resolution) + print(cmd) + os.system(cmd) + os.remove(filelist_name) + + +def m40_generate_points_tfrecords(): + points_folder = os.path.join(root_folder, 'ModelNet40.points') + for folder in ['train', 'test']: + train = folder == 'train' + shuffle = '--shuffle true' if folder == 'train' else '' + filelist, idx = m40_get_filelist( + points_folder, train=train, suffix='points') + filename = os.path.join(root_folder, 'm40_%s_points_list.txt' % folder) + with open(filename, 'w') as fid: + for i in range(len(filelist)): + fid.write('%s %d\n' % (filelist[i], idx[i])) + tfrecords_name = os.path.join( + root_folder, 'm40_%s_points.tfrecords' % folder) + cmd = 'python %s %s --file_dir %s --list_file %s --records_name %s' % \ + (converter, shuffle, points_folder, filename, tfrecords_name) + print(cmd) + os.system(cmd) + + +def m40_generate_octree_tfrecords(depth=5): + # generate octree + m40_convert_points_to_octree(depth, adaptive=0, node_dis=0) + + # generate tfrecords + octree_folder = os.path.join(root_folder, 'ModelNet40.octree.%d' % depth) + for folder in ['train', 'test']: + train = folder == 'train' + shuffle = '--shuffle true' if folder == 'train' else '' + filelist, idx = m40_get_filelist( + octree_folder, train=train, suffix='octree') + filename = os.path.join(root_folder, 'm40_%s_octree_list.txt' % folder) + with open(filename, 'w') as fid: + for i in range(len(filelist)): + fid.write('%s %d\n' % (filelist[i], idx[i])) + tfname = os.path.join( + root_folder, 'm40_%d_2_12_%s_octree.tfrecords' % (depth, folder)) + cmd = 'python %s %s --file_dir %s --list_file %s --records_name %s' % \ + (converter, shuffle, octree_folder, filename, tfname) + print(cmd) + os.system(cmd) + + +if __name__ == '__main__': + eval('%s()' % args.run) diff --git a/tensorflow/data/completion.py b/tensorflow/data/completion.py new file mode 100644 index 0000000..61673bb --- /dev/null +++ b/tensorflow/data/completion.py @@ -0,0 +1,188 @@ +import os +import sys +import argparse + + +parser = argparse.ArgumentParser() +parser.add_argument('--run', type=str, required=True) +parser.add_argument('--octree', type=str, required=False, + default='script/logs/completion/skip_connections_test') +args = parser.parse_args() + +abs_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +root_folder = os.path.join(abs_path, 'script/dataset/ocnn_completion') + +points2ply, ply2points, octree2pts = 'points2ply', 'ply2points', 'octree2points' +converter = os.path.join(abs_path, 'util/convert_tfrecords.py') + + +def download_point_clouds(): + # download via wget + if not os.path.exists(root_folder): + os.makedirs(root_folder) + url = 'https://www.dropbox.com/s/y5ljm2bs8649j3p/ocnn_completion.zip?dl=0' + cmd = 'wget %s -O %s.zip' % (url, root_folder) + print(cmd) + os.system(cmd) + + # unzip + cmd = 'unzip %s.zip -d %s' % (root_folder, root_folder) + print(cmd) + os.system(cmd) + + +def _convert_ply_to_points(prefix='shape'): + ply_folder = os.path.join(root_folder, prefix + '.ply') + points_folder = os.path.join(root_folder, prefix + '.points') + + folders = os.listdir(ply_folder) + for folder in folders: + curr_folder = os.path.join(ply_folder, folder) + + # write filelist to disk + filenames = os.listdir(curr_folder) + filelist_name = os.path.join(curr_folder, 'filelist.txt') + with open(filelist_name, 'w') as fid: + for filename in filenames: + if filename.endswith('.ply'): + fid.write(os.path.join(curr_folder, filename) + '\n') + + # run points2ply + output_path = os.path.join(points_folder, folder) + if not os.path.exists(output_path): + os.makedirs(output_path) + cmd = '%s --filenames %s --output_path %s --verbose 0' % \ + (ply2points, filelist_name, output_path) + print(cmd) + os.system(cmd) + os.remove(filelist_name) + + +def convert_ply_to_points(): + _convert_ply_to_points('shape') + _convert_ply_to_points('test.scans') + + +def _convert_points_to_ply(prefix='shape'): + ply_folder = os.path.join(root_folder, prefix + '.ply') + points_folder = os.path.join(root_folder, prefix + '.points') + + folders = os.listdir(points_folder) + for folder in folders: + curr_folder = os.path.join(points_folder, folder) + + # write filelist to disk + filenames = os.listdir(curr_folder) + filelist_name = os.path.join(curr_folder, 'filelist.txt') + with open(filelist_name, 'w') as fid: + for filename in filenames: + if filename.endswith('.points'): + fid.write(os.path.join(curr_folder, filename) + '\n') + + # run points2ply + output_path = os.path.join(ply_folder, folder) + if not os.path.exists(output_path): + os.makedirs(output_path) + cmd = '%s --filenames %s --output_path %s --verbose 0' % \ + (points2ply, filelist_name, output_path) + print(cmd) + os.system(cmd) + os.remove(filelist_name) + + +def convert_points_to_ply(): + _convert_points_to_ply('shape') + _convert_points_to_ply('test.scans') + + +def generate_points_tfrecords(): + # tfrecords for training + points_folder = os.path.join(root_folder, 'shape.points') + filelist = os.path.join(root_folder, 'filelist_train.txt') + records_name = os.path.join(root_folder, 'completion_train_points.tfrecords') + cmd = 'python %s --file_dir %s --list_file %s --records_name %s' % \ + (converter, points_folder, filelist, records_name) + print(cmd) + os.system(cmd) + + # tfrecords for testing + points_folder = os.path.join(root_folder, 'shape.points') + filelist = os.path.join(root_folder, 'filelist_test.txt') + records_name = os.path.join(root_folder, 'completion_test_points.tfrecords') + cmd = 'python %s --file_dir %s --list_file %s --records_name %s' % \ + (converter, points_folder, filelist, records_name) + print(cmd) + os.system(cmd) + + # tfrecords for testing scans + points_folder = os.path.join(root_folder, 'test.scans.points') + filelist = os.path.join(root_folder, 'filelist_test_scans.txt') + records_name = os.path.join(root_folder, 'completion_test_scans_points.tfrecords') + cmd = 'python %s --file_dir %s --list_file %s --records_name %s' % \ + (converter, points_folder, filelist, records_name) + print(cmd) + os.system(cmd) + + +def generate_dataset(): + download_point_clouds() + convert_ply_to_points() + generate_points_tfrecords() + + +def rename_output_octree(): + filelist = os.path.join(root_folder, 'filelist_test_scans.txt') + filenames = [] + with open(filelist, 'r') as fid: + for line in fid: + filename = line.split()[0] + filenames.append(filename[:-6] + 'octree') + + idx = 0 + folder_in = args.octree + octree_in = os.listdir(folder_in) + octree_in.sort() + folder_out = os.path.join(root_folder, 'output.octree') + for o in octree_in: + if o.endswith('output.octree'): + name_in = os.path.join(folder_in, o) + name_out = os.path.join(folder_out, filenames[idx]) + os.renames(name_in, name_out) + idx += 1 + assert (idx == 1200) + + +def _convert_octree_to_points(suffix='ply'): + octree_folder = os.path.join(root_folder, 'output.octree') + points_folder = os.path.join(root_folder, 'output.' + suffix) + + folders = os.listdir(octree_folder) + for folder in folders: + curr_folder = os.path.join(octree_folder, folder) + + # write filelist to disk + filenames = os.listdir(curr_folder) + filelist_name = os.path.join(curr_folder, 'filelist.txt') + with open(filelist_name, 'w') as fid: + for filename in filenames: + if filename.endswith('.octree'): + fid.write(os.path.join(curr_folder, filename) + '\n') + + # run octree2points + output_path = os.path.join(points_folder, folder) + if not os.path.exists(output_path): + os.makedirs(output_path) + cmd = '%s --filenames %s --output_path %s --verbose 0 --suffix %s' % \ + (octree2pts, filelist_name, output_path, suffix) + print(cmd) + os.system(cmd) + os.remove(filelist_name) + + +def convert_octree_to_points(): + _convert_octree_to_points('points') + _convert_octree_to_points('ply') + + +if __name__ == '__main__': + eval('%s()' % args.run) diff --git a/tensorflow/data/seg_shapenet.py b/tensorflow/data/seg_shapenet.py new file mode 100644 index 0000000..e8f740d --- /dev/null +++ b/tensorflow/data/seg_shapenet.py @@ -0,0 +1,128 @@ +import os +import json + +current_path = os.path.dirname(os.path.realpath(__file__)) +root_folder = os.path.join(current_path, '../script/dataset/shapenet_segmentation') +ply2points = 'ply2points' +convert_tfrecords = os.path.join(current_path, '../util/convert_tfrecords.py') +zip_name = 'shapenetcore_partanno_segmentation_benchmark_v0_normal' + +txt_folder = os.path.join(root_folder, zip_name) +ply_folder = os.path.join(root_folder, 'ply') +points_folder = os.path.join(root_folder, 'points') +dataset_folder = os.path.join(root_folder, 'datasets') + +categories= ['02691156', '02773838', '02954340', '02958343', + '03001627', '03261776', '03467517', '03624134', + '03636649', '03642806', '03790512', '03797390', + '03948459', '04099429', '04225987', '04379243'] +names = ['Aero', 'Bag', 'Cap', 'Car', + 'Chair', 'EarPhone', 'Guitar', 'Knife', + 'Lamp', 'Laptop', 'Motor', 'Mug', + 'Pistol', 'Rocket', 'Skate', 'Table'] +seg_num = [4, 2, 2, 4, 4, 3, 3, 2, 4, 2, 6, 2, 3, 3, 3, 3 ] +dis = [0, 4, 6, 8, 12, 16, 19, 22, 24, 28, 30, 36, 38, 41, 44, 47] + +def download_and_unzip(): + print('Downloading and unzipping ...') + if not os.path.exists(root_folder): os.makedirs(root_folder) + url = 'https://shapenet.cs.stanford.edu/media/%s.zip' % zip_name + os.system('wget %s -P %s' % (url, root_folder)) + os.system('unzip %s.zip -d %s' % (os.path.join(root_folder, zip_name), root_folder)) + +def txt_to_ply(): + print('Convert txt files to ply files ...') + header = 'ply\nformat ascii 1.0\nelement vertex %d\n' + \ + 'property float x\nproperty float y\nproperty float z\n' + \ + 'property float nx\nproperty float ny\nproperty float nz\n' + \ + 'property float label\nelement face 0\n' + \ + 'property list uchar int vertex_indices\nend_header' + for i, c in enumerate(categories): + src_folder = os.path.join(txt_folder, c) + des_folder = os.path.join(ply_folder, c) + if not os.path.exists(des_folder): os.makedirs(des_folder) + + filenames = os.listdir(src_folder) + for filename in filenames: + filename_txt = os.path.join(src_folder, filename) + filename_ply = os.path.join(des_folder, filename[:-4] + '.ply') + with open(filename_txt, 'r') as fid: + lines = [] + for line in fid: + if line == '\n': continue + nums = line.split() + nums[-1] = str(float(nums[-1]) - dis[i]) + lines.append(' '.join(nums)) + + ply_header = header % len(lines) + ply_content = '\n'.join([ply_header] + lines) + with open(filename_ply, 'w') as fid: + fid.write(ply_content) + +def ply_to_points(): + print('Convert ply files to points files ...') + for c in categories: + src_folder = os.path.join(ply_folder, c) + des_folder = os.path.join(points_folder, c) + list_folder = os.path.join(ply_folder, 'fileliet') + if not os.path.exists(des_folder): os.makedirs(des_folder) + if not os.path.exists(list_folder): os.makedirs(list_folder) + + list_filename = os.path.join(list_folder, c + '.txt') + filenames = [os.path.join(src_folder, filename) for filename in os.listdir(src_folder)] + with open(list_filename, 'w') as fid: + fid.write('\n'.join(filenames)) + + cmds = [ply2points, + '--filenames', list_filename, + '--output_path', des_folder, + '--verbose', '0'] + cmd = ' '.join(cmds) + # print(cmd + '\n') + os.system(cmd) + +def points_to_tfrecords(): + print('Convert points files to tfrecords files ...') + if not os.path.exists(dataset_folder): os.makedirs(dataset_folder) + list_folder = os.path.join(txt_folder, 'train_test_split') + train_list_name = os.path.join(list_folder, 'shuffled_train_file_list.json') + val_list_name = os.path.join(list_folder, 'shuffled_val_file_list.json') + test_list_name = os.path.join(list_folder, 'shuffled_test_file_list.json') + with open(train_list_name) as fid: train_list = json.load(fid) + with open(val_list_name) as fid: val_list = json.load(fid) + with open(test_list_name) as fid: test_list = json.load(fid) + for i, c in enumerate(categories): + filelist_name = os.path.join(list_folder, c + '_train_val.txt') + filelist = ['%s.points %d' % (line[11:], i) for line in train_list if c in line] + \ + ['%s.points %d' % (line[11:], i) for line in val_list if c in line] + with open(filelist_name, 'w') as fid: + fid.write('\n'.join(filelist)) + + dataset_name = os.path.join(dataset_folder, c + '_train_val.tfrecords') + cmds = ['python', convert_tfrecords, + '--file_dir', points_folder, + '--list_file', filelist_name, + '--records_name', dataset_name] + cmd = ' '.join(cmds) + print(cmd + '\n') + os.system(cmd) + + filelist_name = os.path.join(list_folder, c + '_test.txt') + filelist = ['%s.points %d' % (line[11:], i) for line in test_list if c in line] + with open(filelist_name, 'w') as fid: + fid.write('\n'.join(filelist)) + + dataset_name = os.path.join(dataset_folder, c + '_test.tfrecords') + cmds = ['python', convert_tfrecords, + '--file_dir', points_folder, + '--list_file', filelist_name, + '--records_name', dataset_name] + cmd = ' '.join(cmds) + print(cmd + '\n') + os.system(cmd) + +if __name__ == '__main__': + download_and_unzip() + txt_to_ply() + ply_to_points() + points_to_tfrecords() diff --git a/tensorflow/libs/.DS_Store b/tensorflow/libs/.DS_Store new file mode 100644 index 0000000..1ff9a70 Binary files /dev/null and b/tensorflow/libs/.DS_Store differ diff --git a/tensorflow/libs/Makefile b/tensorflow/libs/Makefile new file mode 100644 index 0000000..de6d75d --- /dev/null +++ b/tensorflow/libs/Makefile @@ -0,0 +1,92 @@ +TF_CFLAGS := -I/home/qil15006/.conda/envs/tf-OCNN/lib/python3.7/site-packages/tensorflow/include -D_GLIBCXX_USE_CXX11_ABI=1 +TF_LFLAGS := -L/home/qil15006/.conda/envs/tf-OCNN/lib/python3.7/site-packages/tensorflow -l:libtensorflow_framework.so.1 +OCT_CFLAGS := -I../../octree/octree +OCT_LFLAGS := -L../../octree/build -loctree_lib + +NVCC_FLAGS1 := -std=c++11 -O2 -c +NVCC_FLAGS2 := $(TF_CFLAGS) $(OCT_CFLAGS) -I /usr/local/cuda-10.1/include -x cu -Xcompiler -fPIC -D GOOGLE_CUDA=1 -I /usr/local -expt-relaxed-constexpr -DNDEBUG +CC_FLAGS1 := -std=c++11 -O2 +CC_FLAGS2 := $(TF_CFLAGS) $(OCT_CFLAGS) -I /usr/local/cuda-10.1/include -L /usr/local/cuda-10.1/lib64 -D GOOGLE_CUDA=1 $(TF_LFLAGS) $(OCT_LFLAGS) -fPIC -lcudart + +CC := g++ +NVCC := /usr/local/cuda-10.1/bin/nvcc + +cpu_objects := object/octree2col_op.cc.o object/octree_align_op.cc.o object/octree_batch_op.cc.o object/octree_bilinear_op.cc.o object/octree_conv_op.cc.o object/octree_gather_op.cc.o object/octree_grow_op.cc.o object/octree_key_op.cc.o object/octree_mask_op.cc.o object/octree_max_pool_op.cc.o object/octree_new_op.cc.o object/octree_pad_op.cc.o object/octree_property_op.cc.o object/octree_samples.cc.o object/octree_search_op.cc.o object/octree_set_property_op.cc.o object/octree_update_op.cc.o object/points2octree_op.cc.o object/points_property_op.cc.o object/transform_octree_op.cc.o object/transform_points_op.cc.o +gpu_objects := object/tensorflow_gpu_gemm.cu.cc.o + +.PHONY : all clean + +all : libocnn.so + +libocnn.so : $(cpu_objects) $(gpu_objects) + $(CC) $(CC_FLAGS1) -shared -o libocnn.so object/*.o $(CC_FLAGS2) + +object/octree2col_op.cc.o : octree2col_op.cc + $(CC) $(CC_FLAGS1) -c octree2col_op.cc $(CC_FLAGS2) -o object/octree2col_op.cc.o + +object/octree_align_op.cc.o : octree_align_op.cc + $(CC) $(CC_FLAGS1) -c octree_align_op.cc $(CC_FLAGS2) -o object/octree_align_op.cc.o + +object/octree_batch_op.cc.o : octree_batch_op.cc + $(CC) $(CC_FLAGS1) -c octree_batch_op.cc $(CC_FLAGS2) -o object/octree_batch_op.cc.o + +object/octree_bilinear_op.cc.o : octree_bilinear_op.cc + $(CC) $(CC_FLAGS1) -c octree_bilinear_op.cc $(CC_FLAGS2) -o object/octree_bilinear_op.cc.o + +object/octree_conv_op.cc.o : tensorflow_gpu_gemm.h octree_conv_op.cc + $(CC) $(CC_FLAGS1) -c octree_conv_op.cc $(CC_FLAGS2) -o object/octree_conv_op.cc.o + +object/octree_gather_op.cc.o : octree_gather_op.cc + $(CC) $(CC_FLAGS1) -c octree_gather_op.cc $(CC_FLAGS2) -o object/octree_gather_op.cc.o + +object/octree_grow_op.cc.o : octree_grow_op.cc + $(CC) $(CC_FLAGS1) -c octree_grow_op.cc $(CC_FLAGS2) -o object/octree_grow_op.cc.o + +object/octree_key_op.cc.o : octree_key_op.cc + $(CC) $(CC_FLAGS1) -c octree_key_op.cc $(CC_FLAGS2) -o object/octree_key_op.cc.o + +object/octree_mask_op.cc.o : octree_mask_op.cc + $(CC) $(CC_FLAGS1) -c octree_mask_op.cc $(CC_FLAGS2) -o object/octree_mask_op.cc.o + +object/octree_max_pool_op.cc.o : octree_max_pool_op.cc + $(CC) $(CC_FLAGS1) -c octree_max_pool_op.cc $(CC_FLAGS2) -o object/octree_max_pool_op.cc.o + +object/octree_new_op.cc.o : octree_new_op.cc + $(CC) $(CC_FLAGS1) -c octree_new_op.cc $(CC_FLAGS2) -o object/octree_new_op.cc.o + +object/octree_pad_op.cc.o : octree_pad_op.cc + $(CC) $(CC_FLAGS1) -c octree_pad_op.cc $(CC_FLAGS2) -o object/octree_pad_op.cc.o + +object/octree_property_op.cc.o : octree_property_op.cc + $(CC) $(CC_FLAGS1) -c octree_property_op.cc $(CC_FLAGS2) -o object/octree_property_op.cc.o + +object/octree_samples.cc.o : octree_samples.cc + $(CC) $(CC_FLAGS1) -c octree_samples.cc $(CC_FLAGS2) -o object/octree_samples.cc.o + +object/octree_search_op.cc.o : octree_search_op.cc + $(CC) $(CC_FLAGS1) -c octree_search_op.cc $(CC_FLAGS2) -o object/octree_search_op.cc.o + +object/octree_set_property_op.cc.o : octree_set_property_op.cc + $(CC) $(CC_FLAGS1) -c octree_set_property_op.cc $(CC_FLAGS2) -o object/octree_set_property_op.cc.o + +object/octree_update_op.cc.o : octree_update_op.cc + $(CC) $(CC_FLAGS1) -c octree_update_op.cc $(CC_FLAGS2) -o object/octree_update_op.cc.o + +object/points2octree_op.cc.o : points2octree_op.cc + $(CC) $(CC_FLAGS1) -c points2octree_op.cc $(CC_FLAGS2) -o object/points2octree_op.cc.o + +object/points_property_op.cc.o : points_property_op.cc + $(CC) $(CC_FLAGS1) -c points_property_op.cc $(CC_FLAGS2) -o object/points_property_op.cc.o + +object/transform_octree_op.cc.o : transform_octree_op.cc + $(CC) $(CC_FLAGS1) -c transform_octree_op.cc $(CC_FLAGS2) -o object/transform_octree_op.cc.o + +object/transform_points_op.cc.o : transform_points_op.cc + $(CC) $(CC_FLAGS1) -c transform_points_op.cc $(CC_FLAGS2) -o object/transform_points_op.cc.o + +object/tensorflow_gpu_gemm.cu.cc.o : tensorflow_gpu_gemm.h tensorflow_gpu_gemm.cu.cc + $(NVCC) $(NVCC_FLAGS1) tensorflow_gpu_gemm.cu.cc $(NVCC_FLAGS2) -o object/tensorflow_gpu_gemm.cu.cc.o + +clean : + rm object/*.o + diff --git a/tensorflow/libs/__init__.py b/tensorflow/libs/__init__.py new file mode 100644 index 0000000..71f9927 --- /dev/null +++ b/tensorflow/libs/__init__.py @@ -0,0 +1,465 @@ +import os +import tensorflow as tf +from tensorflow.python.framework import ops + + +_current_path = os.path.dirname(os.path.realpath(__file__)) +_tf_ocnn_module = tf.load_op_library(os.path.join(_current_path, 'libocnn.so')) + +# check the modules +# https://stackoverflow.com/questions/42494557/tensorflow-new-op-attributeerror-module-object-has-no-attribute-custom-op +make_grids = _tf_ocnn_module.make_grids # QQ revised: add makegrids +bounding_sphere = _tf_ocnn_module.bounding_sphere +points_property = _tf_ocnn_module.points_property +transform_points = _tf_ocnn_module.transform_points +octree_drop = _tf_ocnn_module.octree_drop +octree_scan = _tf_ocnn_module.octree_scan +octree_cast = _tf_ocnn_module.octree_cast +octree_batch = _tf_ocnn_module.octree_batch +points2octree = _tf_ocnn_module.points_to_octree +octree_property = _tf_ocnn_module.octree_property +octree_pad = _tf_ocnn_module.octree_pad +octree_depad = _tf_ocnn_module.octree_depad +octree2col = _tf_ocnn_module.octree_to_col +col2octree = _tf_ocnn_module.col_to_octree +octree_grow = _tf_ocnn_module.octree_grow +octree_new = _tf_ocnn_module.octree_new +octree_update = _tf_ocnn_module.octree_update +octree_align = _tf_ocnn_module.octree_align +octree_mask = _tf_ocnn_module.octree_mask +octree_samples = _tf_ocnn_module.octree_samples +octree_search = _tf_ocnn_module.octree_search +octree_key2xyz = _tf_ocnn_module.octree_key_to_xyz +octree_xyz2key = _tf_ocnn_module.octree_xyz_to_key +octree_decode_key = _tf_ocnn_module.octree_decode_key +octree_encode_key = _tf_ocnn_module.octree_encode_key +octree_search_key = _tf_ocnn_module.octree_search_key +octree_set_property = _tf_ocnn_module.octree_set_property +octree_gather = _tf_ocnn_module.octree_gather +octree_gatherbk = _tf_ocnn_module.octree_gatherbk +_octree_max_pool = _tf_ocnn_module.octree_max_pool +_octree_mask_pool = _tf_ocnn_module.octree_mask_pool +_octree_max_unpool = _tf_ocnn_module.octree_max_unpool +_octree_conv = _tf_ocnn_module.octree_conv +_octree_deconv = _tf_ocnn_module.octree_deconv +_octree_conv_grad = _tf_ocnn_module.octree_conv_grad +_octree_deconv_grad = _tf_ocnn_module.octree_deconv_grad +_octree_align_grad = _tf_ocnn_module.octree_align_grad +_octree_bilinear = _tf_ocnn_module.octree_bilinear + + +ops.NotDifferentiable('BoundingSphere') +ops.NotDifferentiable('OctreeSetProperty') +ops.NotDifferentiable('OctreeBatch') +ops.NotDifferentiable('TransformPoints') +ops.NotDifferentiable('PointsToOctree') +ops.NotDifferentiable('OctreeProperty') +ops.NotDifferentiable('OctreeNew') +ops.NotDifferentiable('OctreeUpdate') +ops.NotDifferentiable('OctreeGrow') +ops.NotDifferentiable('OctreeSamples') +ops.NotDifferentiable('OctreeBilinear') +ops.NotDifferentiable('OctreeKeyToXyz') +ops.NotDifferentiable('OctreeXyzToKey') +ops.NotDifferentiable('OctreeDecodeKey') +ops.NotDifferentiable('OctreeEncodeKey') +ops.NotDifferentiable('OctreeSearchKey') +ops.NotDifferentiable('OctreeSearch') +ops.NotDifferentiable('PointsProperty') +ops.NotDifferentiable('OctreeScan') +ops.NotDifferentiable('OctreeCast') +ops.NotDifferentiable('OctreeDrop') +ops.NotDifferentiable('MakeGrids') # QQ revised: add makegrids + + + +@ops.RegisterGradient('OctreePad') +def _OctreePadGrad(op, grad): + grad_out = octree_depad(grad, op.inputs[1], op.get_attr('depth')) + return [grad_out, None] + + +@ops.RegisterGradient('OctreeDepad') +def _OctreeDepadGrad(op, grad): + grad_out = octree_pad(grad, op.inputs[1], op.get_attr('depth')) + return [grad_out, None] + + +@ops.RegisterGradient('OctreeToCol') +def _OctreeToColGrad(op, grad): + grad_out = col2octree(grad, op.inputs[1], op.get_attr('depth'), + op.get_attr('kernel_size'), op.get_attr('stride')) + return [grad_out, None] + + +@ops.RegisterGradient('ColToOctree') +def _ColToOctreeGrad(op, grad): + grad_out = octree2col(grad, op.inputs[1], op.get_attr('depth'), + op.get_attr('kernel_size'), op.get_attr('stride')) + return [grad_out, None] + + +@ops.RegisterGradient('OctreeMaxPool') +def _OctreeMaxPoolGrad(op, *grad): + grad_out = _octree_max_unpool(grad[0], op.outputs[1], op.inputs[1], + op.get_attr('depth')) + return [grad_out, None] + + +@ops.RegisterGradient('OctreeMaxUnpool') +def _OctreeMaxUnpoolGrad(op, grad): + grad_out = _octree_mask_pool(grad, op.inputs[1], op.inputs[2], + op.get_attr('depth')) + return [grad_out, None, None] + + +@ops.RegisterGradient('OctreeMaskPool') +def _OctreeMaskPoolGrad(op, grad): + grad_out = _octree_max_unpool(grad, op.inputs[1], op.inputs[2], + op.get_attr('depth')) + return [grad_out, None, None] + + +@ops.RegisterGradient('OctreeConv') +def _OctreeConvGrad(op, grad): + grad_out = _octree_conv_grad(op.inputs[0], op.inputs[1], op.inputs[2], grad, + op.get_attr('depth'), op.get_attr('num_output'), + op.get_attr('kernel_size'), op.get_attr('stride')) + return grad_out + (None, ) + + +@ops.RegisterGradient('OctreeDeconv') +def _OctreeDeconvGrad(op, grad): + grad_out = _octree_deconv_grad(op.inputs[0], op.inputs[1], op.inputs[2], grad, + op.get_attr('depth'), op.get_attr('num_output'), + op.get_attr('kernel_size'), op.get_attr('stride')) + return grad_out + (None, ) + + +@ops.RegisterGradient('OctreeAlign') +def _OctreeAlignGrad(op, *grad): + grad_out = _octree_align_grad(grad[0], op.outputs[1]) + return [grad_out, None, None] + + +@ops.RegisterGradient('OctreeMask') +def _OctreeMaskGrad(op, grad): + grad_out = octree_mask(grad, op.inputs[1], op.get_attr('mask')) + return [grad_out, None] + + +@ops.RegisterGradient('OctreeGather') +def _OctreeGatherGrad(op, grad): + shape = tf.shape(op.inputs[0]) + grad_out = octree_gatherbk(grad, op.inputs[1], shape) + return [grad_out, None] + + +def octree_max_pool(data, octree, depth): + with tf.variable_scope('octree_max_pool'): + data, mask = _octree_max_pool(data, octree, depth) # the bottom data depth + data = octree_pad(data, octree, depth-1) # !!! depth-1 + return data, mask + + +def octree_max_unpool(data, mask, octree, depth): + with tf.variable_scope('octree_max_unpool'): + data = octree_depad(data, octree, depth) # !!! depth + data = _octree_max_unpool(data, mask, octree, depth) # the bottom data depth + return data + + +def octree_avg_pool(data, octree, depth): + with tf.variable_scope('octree_avg_pool'): + data = tf.reshape(data, [1, int(data.shape[1]), -1, 8]) + data = tf.reduce_mean(data, axis=3, keepdims=True) + data = octree_pad(data, octree, depth-1) # !!! depth-1 + return data + + +# todo: merge octree_conv_fast and octree_conv_memory to reduce code redundancy +def octree_conv_fast(data, octree, depth, channel, kernel_size=[3], stride=1): + assert(type(kernel_size) is list and len(kernel_size) < 4) + for i in range(len(kernel_size), 3): + kernel_size.append(kernel_size[-1]) + + with tf.variable_scope('octree_conv'): + dim = int(data.shape[1]) * kernel_size[0] * kernel_size[1] * kernel_size[2] + kernel = tf.get_variable('weights', shape=[channel, dim], dtype=tf.float32, + initializer=tf.contrib.layers.xavier_initializer()) + col = octree2col(data, octree, depth, kernel_size, stride) + col = tf.reshape(col, [dim, -1]) + conv = tf.matmul(kernel, col) + conv = tf.expand_dims(tf.expand_dims(conv, 0), -1) # [C, H] -> [1, C, H, 1] + if stride == 2: + conv = octree_pad(conv, octree, depth-1, 0) + return conv + + +def octree_conv_memory(data, octree, depth, channel, kernel_size=[3], stride=1): + assert(type(kernel_size) is list and len(kernel_size) < 4) + for i in range(len(kernel_size), 3): + kernel_size.append(kernel_size[-1]) + + with tf.variable_scope('octree_conv'): + dim = int(data.shape[1]) * kernel_size[0] * kernel_size[1] * kernel_size[2] + kernel = tf.get_variable('weights', shape=[channel, dim], dtype=tf.float32, + initializer=tf.contrib.layers.xavier_initializer()) + conv = _octree_conv(data, kernel, octree, depth, channel, kernel_size, stride) + if stride == 2: + conv = octree_pad(conv, octree, depth-1) + return conv + + +def octree_deconv_fast(data, octree, depth, channel, kernel_size=[3], stride=1): + assert(type(kernel_size) is list and len(kernel_size) < 4) + for i in range(len(kernel_size), 3): + kernel_size.append(kernel_size[-1]) + + with tf.variable_scope('octree_deconv'): + kernel_sdim = kernel_size[0] * kernel_size[1] * kernel_size[2] + dim = channel * kernel_sdim + kernel = tf.get_variable('weights', shape=[int(data.shape[1]), dim], dtype=tf.float32, + initializer=tf.contrib.layers.xavier_initializer()) + if stride == 2: + data = octree_depad(data, octree, depth) + depth = depth + 1 + data = tf.squeeze(data, [0, 3]) + deconv = tf.matmul(kernel, data, transpose_a=True, transpose_b=False) + deconv = tf.reshape(deconv, [channel, kernel_sdim, -1]) + col = col2octree(deconv, octree, depth, kernel_size, stride) + return col + + +def octree_deconv_memory(data, octree, depth, channel, kernel_size=[3], stride=1): + assert(type(kernel_size) is list and len(kernel_size) < 4) + for i in range(len(kernel_size), 3): + kernel_size.append(kernel_size[-1]) + + with tf.variable_scope('octree_deconv'): + kernel_sdim = kernel_size[0] * kernel_size[1] * kernel_size[2] + dim = channel * kernel_sdim + kernel = tf.get_variable('weights', shape=[int(data.shape[1]), dim], dtype=tf.float32, + initializer=tf.contrib.layers.xavier_initializer()) + if stride == 2: + data = octree_depad(data, octree, depth) + deconv = _octree_deconv(data, kernel, octree, depth, channel, kernel_size, stride) + return deconv + + +def octree_full_voxel(data, depth): + height = 2 ** (3 * depth) + channel = int(data.shape[1]) + with tf.variable_scope('octree_full_voxel'): + data = tf.reshape(data, [channel, -1, height]) # (1, C, H, 1) -> (C, batch_size, H1) + data = tf.transpose(data, perm=[1, 0, 2]) + return data + + +def octree_tile(data, octree, depth): + with tf.variable_scope('octree_tile'): + data = octree_depad(data, octree, depth) # (1, C, H, 1) + data = tf.tile(data, [1, 1, 1, 8]) # (1, C, H, 8) + channel = int(data.shape[1]) + output = tf.reshape(data, [1, channel, -1, 1]) + return output + + +def octree_global_pool(data, octree, depth): + with tf.variable_scope('octree_global_pool'): + segment_ids = octree_property(octree, property_name='index', dtype=tf.int32, + depth=depth, channel=1) + segment_ids = tf.reshape(segment_ids, [-1]) + data = tf.squeeze(data, axis=[0, 3]) # (1, C, H, 1) -> (C, H) + data = tf.transpose(data) # (C, H) -> (H, C) + output = tf.math.segment_mean(data, segment_ids) # (H, C) -> (batch_size, C) + return output + + +def octree_bilinear_legacy(data, octree, depth, target_depth): + with tf.variable_scope('octree_bilinear'): + mask = tf.constant( + [[0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0], + [0, 1, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1]], dtype=tf.float32) + index, fracs = _octree_bilinear(octree, depth, target_depth) + feat = tf.transpose(tf.squeeze(data, [0, 3])) # (1, C, H, 1) -> (H, C) + output = tf.zeros([tf.shape(index)[0], tf.shape(feat)[1]], dtype=tf.float32) + norm = tf.zeros([tf.shape(index)[0], 1], dtype=tf.float32) + for i in range(8): + idxi = index[:, i] + weight = tf.abs(tf.reduce_prod(mask[i, :] - fracs, axis=1, keepdims=True)) + output += weight * tf.gather(feat, idxi) + norm += weight * tf.expand_dims(tf.cast(idxi > -1, dtype=tf.float32), -1) + output = tf.div(output, norm) + output = tf.expand_dims(tf.expand_dims(tf.transpose(output), 0), -1) + return output + + +# pts: (N, 4), i.e. N x (x, y, z, id) +# data: (1, C, H, 1) +def octree_bilinear_v1(pts, data, octree, depth): + with tf.variable_scope('octree_bilinear'): + mask = tf.constant( + [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], + [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]], dtype=tf.float32) + + xyzf, ids = tf.split(pts, [3, 1], 1) + xyzf = xyzf - 0.5 # since the value is defined on the center of each voxel + xyzi = tf.floor(xyzf) # the integer part + frac = xyzf - xyzi # the fraction part + + feat = tf.transpose(tf.squeeze(data, [0, 3])) # (1, C, H, 1) -> (H, C) + output = tf.zeros([tf.shape(xyzi)[0], tf.shape(feat)[1]], dtype=tf.float32) + norm = tf.zeros([tf.shape(xyzi)[0], 1], dtype=tf.float32) + + for i in range(8): + maski = mask[i, :] + maskc = 1.0 - maski + xyzm = xyzi + maski + xyzm = tf.cast(tf.concat([xyzm, ids], axis=1), dtype=tf.uint8) + idxi = octree_search_key(octree_encode_key(xyzm), octree, depth, is_xyz=True) + + weight = tf.abs(tf.reduce_prod(maskc - frac, axis=1, keepdims=True)) + output += weight * tf.gather(feat, idxi) + norm += weight * tf.expand_dims(tf.cast(idxi > -1, dtype=tf.float32), -1) + output = tf.div(output, norm) + + output = tf.expand_dims(tf.expand_dims(tf.transpose(output), 0), -1) + frac = tf.expand_dims(tf.expand_dims(tf.transpose(frac), 0), -1) + + return output, frac + +# pts: (N, 4), i.e. N x (x, y, z, id) +# data: (1, C, H, 1) +def octree_bilinear_v2(pts, data, octree, depth): + with tf.variable_scope('octree_bilinear'): + mask = tf.constant( + [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], + [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]], dtype=tf.float32) + + xyzf, ids = tf.split(pts, [3, 1], 1) + xyzf = xyzf - 0.5 # since the value is defined on the center of each voxel + xyzi = tf.floor(xyzf) # the integer part + frac = xyzf - xyzi # the fraction part + + output = tf.zeros([1, tf.shape(data)[1], tf.shape(xyzi)[0], 1], dtype=tf.float32) + norm = tf.zeros([tf.shape(xyzi)[0], 1], dtype=tf.float32) + + for i in range(8): + maski = mask[i, :] + maskc = 1.0 - maski + xyzm = xyzi + maski + xyzm = tf.cast(tf.concat([xyzm, ids], axis=1), dtype=tf.uint8) + # !!! Note some elements of idxi may be -1 + idxi = octree_search_key(octree_encode_key(xyzm), octree, depth, is_xyz=True) + + weight = tf.abs(tf.reduce_prod(maskc - frac, axis=1, keepdims=True)) + # output += weight * tf.gather(data, idxi, axis=2) + output += weight * octree_gather(data, idxi) + norm += weight * tf.expand_dims(tf.cast(idxi > -1, dtype=tf.float32), -1) + output = tf.div(output, norm) + return output + + +# pts: (N, 4), i.e. N x (x, y, z, id). +# data: (1, C, H, 1) +# !!! Note: the pts should be scaled into [0, 2^depth] +def octree_bilinear_v3(pts, data, octree, depth): + with tf.variable_scope('octree_linear'): + mask = tf.constant( + [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], + [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]], dtype=tf.float32) + masku = tf.constant([0, 65536, 256, 65792, 1, 65537, 257, 65793], dtype=tf.int32) + maskc = 1 - mask + + xyzf, ids = tf.split(pts, [3, 1], 1) + xyzf = xyzf - 0.5 # since the value is defined on the center of each voxel + xyzi = tf.floor(xyzf) # the integer part (N, 3) + frac = xyzf - xyzi # the fraction part (N, 3) + + key = tf.cast(tf.concat([xyzi, ids], axis=1), dtype=tf.uint8) + key = tf.cast(octree_encode_key(key), dtype=tf.int32) + # Cast the key to `int32` since the `add` below does not support `uint32` + # The size effect is that the batch_size must be smaller than 128 + key = tf.expand_dims(key, 1) + masku # (N, 8), + key = tf.cast(tf.reshape(key, [-1]), dtype=tf.uint32) + + idx = octree_search_key(key, octree, depth) # (N*8,) + flgs = idx > -1 # filtering flags + idx = tf.boolean_mask(idx, flgs) + + npt = tf.shape(xyzi)[0] + ids = tf.reshape(tf.range(npt), [-1, 1]) + ids = tf.reshape(tf.tile(ids, [1, 8]), [-1]) # (N*8,) + ids = tf.boolean_mask(ids, flgs) + + frac = maskc - tf.expand_dims(frac, axis=1) + weight = tf.abs(tf.reshape(tf.reduce_prod(frac, axis=2), [-1])) + weight = tf.boolean_mask(weight, flgs) + + indices = tf.concat([tf.expand_dims(ids, 1), tf.expand_dims(idx, 1)], 1) + indices = tf.cast(indices, tf.int64) + data = tf.squeeze(data, [0, 3]) # (C, H) + h = tf.shape(data)[1] + mat = tf.SparseTensor(indices=indices, values=weight, dense_shape=[npt, h]) + + # channel, max_channel = int(data.shape[0]), 512 + # if channel > max_channel: + # num = channel // max_channel + # remain = channel % max_channel + # splits = [max_channel] * num + # if remain != 0: + # splits.append(remain) + # num += 1 + # output_split = [None] * num + # data_split = tf.split(data, splits, axis=0) + # for i in range(num): + # with tf.name_scope('mat_%d' % i): + # output_split[i] = tf.sparse.sparse_dense_matmul( + # mat, data_split[i], adjoint_a=False, adjoint_b=True) + # output = tf.concat(output_split, axis=1) + # else: + # output = tf.sparse.sparse_dense_matmul(mat, data, adjoint_a=False, adjoint_b=True) + + output = tf.sparse.sparse_dense_matmul(mat, data, adjoint_a=False, adjoint_b=True) + norm = tf.sparse.sparse_dense_matmul(mat, tf.ones([h, 1])) + output = tf.div(output, norm + 1.0e-10) # avoid dividing by zeros + output = tf.expand_dims(tf.expand_dims(tf.transpose(output), 0), -1) + return output + + +def octree_bilinear(data, octree, depth, target_depth, mask=None): + with tf.name_scope('Octree_bilinear'): + xyz = octree_property(octree, property_name='xyz', depth=target_depth, + channel=1, dtype=tf.uint32) + xyz = tf.reshape(xyz, [-1]) + if mask is not None: xyz = tf.boolean_mask(xyz, mask) + xyz = tf.cast(octree_decode_key(xyz), dtype=tf.float32) + + # Attention: displacement 0.5, scale + scale = 2.0**(depth-target_depth) + xyz += tf.constant([0.5, 0.5, 0.5, 0.0], dtype=tf.float32) + xyz *= tf.constant([scale, scale, scale, 1.0], dtype=tf.float32) + + output = octree_bilinear_v3(xyz, data, octree, depth) + return output + + +# pts: (N, 4), i.e. N x (x, y, z, id) +# data: (1, C, H, 1) +def octree_nearest_interp(pts, data, octree, depth): + with tf.variable_scope('octree_nearest_interp'): + # The value is defined on the center of each voxel, + # so we can get the closest grid point by simply casting the value to uint8 + pts = tf.cast(pts, dtype=tf.uint8) + key = tf.reshape(octree_encode_key(pts), [-1]) + + idx = octree_search_key(key, octree, depth) + # !!! Note that some of idx may be -1 or over-bound + # Use tf.gather may be problematic with some version of tensorflow + # according to my experiments. So I implemented octree_gather to + # replace the original tf.gather. If you encounter errors, please + # use the octree_gather + # output = tf.gather(data, idx, axis=2) + output = octree_gather(data, idx) + return output diff --git a/tensorflow/libs/build.py b/tensorflow/libs/build.py new file mode 100644 index 0000000..20862ff --- /dev/null +++ b/tensorflow/libs/build.py @@ -0,0 +1,96 @@ +import os +import sys +import tensorflow as tf +import subprocess + +OCTREE_DIR = '../../octree' +CUDA_DIR = '/usr/local/cuda-10.1' +if len(sys.argv) > 1: OCTREE_DIR = sys.argv[1] +if len(sys.argv) > 2: CUDA_DIR = sys.argv[2] + + +lines = [] + +TF_CFLAGS = " ".join(tf.sysconfig.get_compile_flags()) +TF_LFLAGS = " ".join(tf.sysconfig.get_link_flags()) +lines.append("TF_CFLAGS := %s" % TF_CFLAGS) +lines.append("TF_LFLAGS := %s" % TF_LFLAGS) +lines.append("OCT_CFLAGS := -I%s/octree" % OCTREE_DIR) +lines.append("OCT_LFLAGS := -L%s/build -loctree_lib" % OCTREE_DIR) +lines.append("") + +lines.append("NVCC_FLAGS1 := -std=c++11 -O2 -c") +lines.append("NVCC_FLAGS2 := $(TF_CFLAGS) $(OCT_CFLAGS) -I %s/include -x cu -Xcompiler -fPIC -D GOOGLE_CUDA=1 -I /usr/local -expt-relaxed-constexpr -DNDEBUG" % CUDA_DIR) +lines.append("CC_FLAGS1 := -std=c++11 -O2") +lines.append("CC_FLAGS2 := $(TF_CFLAGS) $(OCT_CFLAGS) -I %s/include -L %s/lib64 -D GOOGLE_CUDA=1 $(TF_LFLAGS) $(OCT_LFLAGS) -fPIC -lcudart" % (CUDA_DIR, CUDA_DIR)) +lines.append("") + +lines.append("CC := g++") +lines.append("NVCC := %s/bin/nvcc" % CUDA_DIR) +lines.append("") + +cpu_objects = [] +cpu_sources = [] +gpu_objects = [] +gpu_sources = [] +all_headers = [] +for filename in sorted(os.listdir(".")): + if filename.endswith(".cu.cc") or filename.endswith(".cu"): + targetname = filename + ".o" + gpu_sources.append(filename) + gpu_objects.append(os.path.join("object", targetname)) + elif filename.endswith(".cc") or filename.endswith(".cpp"): + targetname = filename + ".o" + cpu_sources.append(filename) + cpu_objects.append(os.path.join("object", targetname)) + elif filename.endswith(".h"): + all_headers.append(filename) + +lines.append("cpu_objects := %s" % " ".join(cpu_objects)) +lines.append("gpu_objects := %s" % " ".join(gpu_objects)) +lines.append("") + +lines.append(".PHONY : all clean") +lines.append("") + +lines.append("all : libocnn.so") +lines.append("") + +lines.append("libocnn.so : $(cpu_objects) $(gpu_objects)") +lines.append("\t$(CC) $(CC_FLAGS1) -shared -o libocnn.so object/*.o $(CC_FLAGS2)") +lines.append("") + +for i in range(len(cpu_objects)): + dependency = b" ".join(subprocess.Popen("g++ -std=c++11 -MM -MG %s" % cpu_sources[i], stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True).stdout.readlines()).decode("utf-8") + headers = [] + for h in all_headers: + if dependency.find(h) > 0: + headers.append(h) + headers = " ".join(headers) + lines.append("%s : %s %s" % (cpu_objects[i], headers, cpu_sources[i])) + lines.append("\t$(CC) $(CC_FLAGS1) -c %s $(CC_FLAGS2) -o %s" % (cpu_sources[i], cpu_objects[i])) + lines.append("") + +for i in range(len(gpu_objects)): + dependency = b" ".join(subprocess.Popen("g++ -std=c++11 -MM -MG %s" % gpu_sources[i], stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True).stdout.readlines()).decode("utf-8") + headers = [] + for h in all_headers: + if dependency.find(h) > 0: + headers.append(h) + headers = " ".join(headers) + lines.append("%s : %s %s" % (gpu_objects[i], headers, gpu_sources[i])) + lines.append("\t$(NVCC) $(NVCC_FLAGS1) %s $(NVCC_FLAGS2) -o %s" % (gpu_sources[i], gpu_objects[i])) + lines.append("") + +lines.append("clean :") +lines.append("\t rm %s" % os.path.join("object", "*.o")) +lines.append("") + +lines = [line + "\n" for line in lines] + +with open("Makefile", "w") as f: + f.writelines(lines) + +if not os.path.exists("object"): + os.mkdir("object") +os.system("make -j all") diff --git a/tensorflow/libs/octree2col_op.cc b/tensorflow/libs/octree2col_op.cc new file mode 100644 index 0000000..a079682 --- /dev/null +++ b/tensorflow/libs/octree2col_op.cc @@ -0,0 +1,169 @@ +#include "octree_nn.h" +#include "octree_parser.h" + +#include +#include +#include + +namespace tensorflow { + +REGISTER_OP("OctreeToCol") + .Input("btm_data: float") + .Input("in_octree: int8") + .Attr("depth: int") + .Attr("kernel_size: list(int)") + .Attr("stride: int") + .Output("top_data: float") + .SetShapeFn([](shape_inference::InferenceContext* c) { + // (1, C, H, 1) -> (C, kernel_dim, H') + vector kernel_size; + TF_RETURN_IF_ERROR(c->GetAttr("kernel_size", &kernel_size)); + resize_with_last_val(kernel_size, 3); + int kernel_dim = num_elements(kernel_size); + c->set_output(0, + c->MakeShape({c->Dim(c->input(0), 1), kernel_dim, c->UnknownDim()})); + return Status::OK(); + }) + .Doc(R"doc(Octree2col operator.)doc"); + + +REGISTER_OP("ColToOctree") + .Input("top_grad: float") + .Input("in_octree: int8") + .Attr("depth: int") + .Attr("kernel_size: list(int)") + .Attr("stride: int") + .Output("btm_grad: float") + .SetShapeFn([](shape_inference::InferenceContext* c) { + // (C, kernel_dim, H) -> (1, C, H', 1) + c->set_output(0, + c->MakeShape({1, c->Dim(c->input(0), 0), c->UnknownDim(), 1})); + return Status::OK(); + }) + .Doc(R"doc(Gradient for octree convolution operator.)doc"); + + +class Octree2ColBase : public OpKernel { + public: + explicit Octree2ColBase(OpKernelConstruction* context) + : OpKernel(context) { + // get attributes + OP_REQUIRES_OK(context, context->GetAttr("depth", &depth_)); + OP_REQUIRES_OK(context, context->GetAttr("stride", &stride_)); + OP_REQUIRES_OK(context, context->GetAttr("kernel_size", &kernel_size_)); + resize_with_last_val(kernel_size_, 3); + + CHECK_GT(depth_, 0) << "Depth should be larger than 0"; + for(auto k : kernel_size_) { CHECK(0 < k && k < 4) << "Invalide kernel size"; } + CHECK(stride_ == 1 || stride_ == 2) << "Unsupport stride"; + } + + void init_ni_ptr(OpKernelContext* ctx, Tensor& ni_gpu) { + vector& ni_cpu = NeighHelper::Get().get_ni(kernel_size_); + int count = ni_cpu.size(); + OP_REQUIRES_OK(ctx, ctx->allocate_temp(DT_INT32, TensorShape({ count }), &ni_gpu)); + // int* ni_ptr = ni_gpu.flat().data(); + // cudaMemcpy(ni_ptr, ni_cpu.data(), sizeof(int) * count, cudaMemcpyHostToDevice); + memcpy_gpu(count, ni_cpu.data(), ni_gpu.flat().data()); + } + + void set_octree_parser(OpKernelContext* context, OctreeParser& octree_) { + auto octree_ptr = context->input(1).flat().data(); + octree_.set_gpu(octree_ptr); + } + + protected: + int depth_; + int stride_; + vector kernel_size_; +}; + + +class OctreeToColOp : public Octree2ColBase { + public: + explicit OctreeToColOp(OpKernelConstruction* context) + : Octree2ColBase(context) {} + + void Compute(OpKernelContext* context) override { + // init + OctreeParser octree_; + this->set_octree_parser(context, octree_); + Tensor ni_gpu; + this->init_ni_ptr(context, ni_gpu); + auto ni_ptr =ni_gpu.flat().data(); + + // input data, data format: [1, channels, H, 1] + const Tensor& btm_data = context->input(0); + const TensorShape& btm_shape = btm_data.shape(); + int btm_depth = this->depth_; + int channel = btm_shape.dim_size(1); + int btm_height = btm_shape.dim_size(2); + CHECK_EQ(octree_.info().node_num(btm_depth), btm_height); + + // output data + int top_height = btm_height; + if (this->stride_ == 2) { + top_height = btm_height / 8; + int top_depth = btm_depth - 1; + CHECK_EQ(top_height, octree_.info().node_num_nempty(top_depth)); + } + Tensor* top_data = nullptr; + int kernel_sdim = num_elements(this->kernel_size_); + TensorShape top_shape({channel, kernel_sdim, top_height}); + OP_REQUIRES_OK(context, context->allocate_output(0, top_shape, &top_data)); + + // execute + auto btm_ptr = btm_data.flat().data(); + auto top_ptr = top_data->flat().data(); + octree2col_gpu(top_ptr, btm_ptr, channel, top_height, + kernel_sdim, this->stride_, octree_.neighbor_gpu(btm_depth), + ni_ptr, top_height, 0); + } +}; + + +class ColToOctreeOp : public Octree2ColBase { + public: + explicit ColToOctreeOp(OpKernelConstruction* context) + : Octree2ColBase(context) {} + + void Compute(OpKernelContext* context) override { + // init + OctreeParser octree_; + this->set_octree_parser(context, octree_); + Tensor ni_gpu; + this->init_ni_ptr(context, ni_gpu); + auto ni_ptr =ni_gpu.flat().data(); + + // in grad + const Tensor& top_grad = context->input(0); + const TensorShape& top_shape = top_grad.shape(); + int channel = top_shape.dim_size(0); + int top_height = top_shape.dim_size(2); + + // out grad + int btm_depth = this->depth_; + int btm_height = octree_.info().node_num(btm_depth); + if (this->stride_ == 2) { + CHECK_EQ(top_height, octree_.info().node_num_nempty(btm_depth - 1)); + } + Tensor* btm_grad = nullptr; + TensorShape btm_shape({1, channel, btm_height, 1}); + OP_REQUIRES_OK(context, context->allocate_output(0, btm_shape, &btm_grad)); + + // execute + auto top_ptr = top_grad.flat().data(); + auto btm_ptr = btm_grad->flat().data(); + // int kernel_size = this->kernel_size_; + int kernel_sdim = num_elements(this->kernel_size_); + col2octree_gpu(top_ptr, btm_ptr, channel, top_height, + kernel_sdim, this->stride_, octree_.neighbor_gpu(btm_depth), + ni_ptr, top_height, 0); + } +}; + + +REGISTER_KERNEL_BUILDER(Name("OctreeToCol").Device(DEVICE_GPU), OctreeToColOp); +REGISTER_KERNEL_BUILDER(Name("ColToOctree").Device(DEVICE_GPU), ColToOctreeOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/octree_align_op.cc b/tensorflow/libs/octree_align_op.cc new file mode 100644 index 0000000..a9b151c --- /dev/null +++ b/tensorflow/libs/octree_align_op.cc @@ -0,0 +1,134 @@ +#include +#include +#include +#include + +#include "octree_nn.h" +#include "octree_parser.h" + +namespace tensorflow { + +REGISTER_OP("OctreeAlign") + .Input("in_data: float") + .Input("src_octree: int8") + .Input("des_octree: int8") + .Attr("depth: int") + .Output("out_data: float") + .Output("key_index: int32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + auto shape = c->input(0); + TF_RETURN_IF_ERROR(c->ReplaceDim(shape, 2, c->UnknownDim(), &shape)); + c->set_output(0, shape); + c->set_output(1, c->MakeShape({c->UnknownDim()})); + return Status::OK(); + }) + .Doc(R"doc(Octree align operator.)doc"); + +REGISTER_OP("OctreeAlignGrad") + .Input("in_data: float") + .Input("key_index: int32") + .Output("out_data: float") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + auto shape = c->input(0); + TF_RETURN_IF_ERROR(c->ReplaceDim(shape, 2, c->UnknownDim(), &shape)); + c->set_output(0, shape); + return Status::OK(); + }) + .Doc(R"doc(Octree align grad operator.)doc"); + +class OctreeAlignOp : public OpKernel { + public: + explicit OctreeAlignOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("depth", &curr_depth_)); + } + + void Compute(OpKernelContext* context) override { + // in data + const Tensor& src_data = context->input(0); + auto src_ptr = src_data.flat().data(); + int src_h = src_data.dim_size(2); + int channel = src_data.dim_size(1); + + // octrees + OctreeParser src_octree, des_octree; + src_octree.set_gpu(context->input(1).flat().data()); + des_octree.set_gpu(context->input(2).flat().data()); + int des_h = des_octree.info().node_num(curr_depth_); + CHECK_EQ(src_octree.info().node_num(curr_depth_), src_h); + + // get key + const uint32* src_key = src_octree.key_gpu(curr_depth_); + Tensor src_key_tensor; + if (src_octree.info().is_key2xyz()) { + xyz2key_gpu_op(context, &src_key_tensor, src_key, src_h, curr_depth_); + src_key = src_key_tensor.flat().data(); + } + const uint32* des_key = des_octree.key_gpu(curr_depth_); + Tensor des_key_tensor; + if (des_octree.info().is_key2xyz()) { + xyz2key_gpu_op(context, &des_key_tensor, des_key, des_h, curr_depth_); + des_key = des_key_tensor.flat().data(); + } + + // binary search + Tensor* idx_tensor = nullptr; + TensorShape idx_shape({src_h}); + OP_REQUIRES_OK(context, context->allocate_output(1, idx_shape, &idx_tensor)); + auto idx_ptr = idx_tensor->flat().data(); + search_key_gpu(idx_ptr, des_key, des_h, src_key, src_h); + + // out data + Tensor* des_tensor = nullptr; + TensorShape des_shape({1, channel, des_h, 1}); + OP_REQUIRES_OK(context, context->allocate_output(0, des_shape, &des_tensor)); + auto des_ptr = des_tensor->flat().data(); + + // exec + align_forward_gpu(des_ptr, des_h, channel, src_ptr, src_h, idx_ptr); + } + + protected: + void xyz2key_gpu_op(OpKernelContext* context, Tensor* key_tensor, + const uint32* xyz, const int num, const int depth) { + OP_REQUIRES_OK(context, context->allocate_temp( + DT_UINT32, TensorShape({num}), key_tensor)); + auto ptr = key_tensor->flat().data(); + xyz2key_gpu(ptr, xyz, num, depth); + } + + private: + int curr_depth_; +}; + +class OctreeAlignGradOp : public OpKernel { + public: + explicit OctreeAlignGradOp(OpKernelConstruction* context) + : OpKernel(context) {} + + void Compute(OpKernelContext* context) override { + // gradients + const Tensor& des_data = context->input(0); + auto des_ptr = des_data.flat().data(); + int channel = des_data.dim_size(1); + int des_h = des_data.dim_size(2); + + // index + const Tensor& idx_tensor = context->input(1); + int src_h = idx_tensor.dim_size(0); + auto idx_ptr = idx_tensor.flat().data(); + + // grad out + Tensor* src_tensor = nullptr; + TensorShape src_shape({1, channel, src_h, 1}); + OP_REQUIRES_OK(context, context->allocate_output(0, src_shape, &src_tensor)); + auto src_ptr = src_tensor->flat().data(); + + // exec + align_backward_gpu(des_ptr, des_h, channel, src_ptr, src_h, idx_ptr); + } +}; + +REGISTER_KERNEL_BUILDER(Name("OctreeAlign").Device(DEVICE_GPU), OctreeAlignOp); +REGISTER_KERNEL_BUILDER(Name("OctreeAlignGrad").Device(DEVICE_GPU), OctreeAlignGradOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/octree_batch_op.cc b/tensorflow/libs/octree_batch_op.cc new file mode 100644 index 0000000..d424180 --- /dev/null +++ b/tensorflow/libs/octree_batch_op.cc @@ -0,0 +1,52 @@ +#include "merge_octrees.h" + +#include +#include +#include + + +namespace tensorflow { + +REGISTER_OP("OctreeBatch") + .Input("batch_data: string") + .Output("octree: int8") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->UnknownShapeOfRank(1)); + return Status::OK(); + }) + .Doc(R"doc(Merge a batch of octrees.)doc"); + + +class OctreeBatchOp : public OpKernel { + public: + explicit OctreeBatchOp(OpKernelConstruction* context) + : OpKernel(context) {} + + void Compute(OpKernelContext* context) override { + // input octrees + const Tensor& data_in = context->input(0); + auto octree_buffer = data_in.flat(); + // int batch_size = data_in.shape().dim_size(0); + int batch_size = data_in.shape().num_elements(); + vector octrees_in; + for (int i = 0; i < batch_size; ++i) { + octrees_in.push_back(octree_buffer(i).data()); + } + + // merge octrees + vector octree_out; + merge_octrees(octree_out, octrees_in); + + // copy output + Tensor* out_data = nullptr; + TensorShape out_shape({ (long long int) octree_out.size() }); + OP_REQUIRES_OK(context, context->allocate_output(0, out_shape, &out_data)); + auto out_ptr = out_data->flat().data(); + memcpy(out_ptr, octree_out.data(), octree_out.size()); + } +}; + + +REGISTER_KERNEL_BUILDER(Name("OctreeBatch").Device(DEVICE_CPU), OctreeBatchOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/octree_bilinear_op.cc b/tensorflow/libs/octree_bilinear_op.cc new file mode 100644 index 0000000..8ec89c7 --- /dev/null +++ b/tensorflow/libs/octree_bilinear_op.cc @@ -0,0 +1,111 @@ +#include "octree_nn.h" +#include "octree_parser.h" + +#include +#include +#include + +namespace tensorflow { + +REGISTER_OP("OctreeBilinear") + .Input("octree: int8") + .Attr("curr_depth: int") + .Attr("target_depth: int") + .Output("index: int32") + .Output("fracs: float") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->MakeShape({ c->UnknownDim(), 8 })); + c->set_output(1, c->MakeShape({ c->UnknownDim(), 3 })); + return Status::OK(); + }) + .Doc(R"doc(Octree bilinear operator.)doc"); + + +class OctreeBilinearOp : public OpKernel { + public: + explicit OctreeBilinearOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("curr_depth", &curr_depth_)); + OP_REQUIRES_OK(context, context->GetAttr("target_depth", &target_depth_)); + CHECK_GT(curr_depth_, 0) + << "The curr_depth should be larger than 0"; + CHECK_GT(target_depth_, curr_depth_) + << "The target_depth should be larger than curr_depth"; + } + + void Compute(OpKernelContext* context) override { + // octree + OctreeParser octree_; + octree_.set_gpu(context->input(0).flat().data()); + CHECK_LE(target_depth_, octree_.info().depth()) + << "The target_depth should be smaller than the octree depth"; + + // get key & xyz + Tensor src_buffer; + int src_h = octree_.info().node_num(curr_depth_); + const uint32* src_key = octree_.key_gpu(curr_depth_); + if (octree_.info().is_key2xyz()) { + xyz2key_gpu_op(context, &src_buffer, src_key, src_h, curr_depth_); + src_key = src_buffer.flat().data(); + } + + Tensor des_buffer; + int des_h = octree_.info().node_num(target_depth_); + const uint32* des_xyz = octree_.key_gpu(target_depth_); + if (!octree_.info().is_key2xyz()) { + key2xyz_gpu_op(context, &des_buffer, des_xyz, des_h, target_depth_); + des_xyz = des_buffer.flat().data(); + } + + // out data + Tensor* idx_tensor = nullptr; + TensorShape idx_shape({ des_h, 8}); + OP_REQUIRES_OK(context, context->allocate_output(0, idx_shape, &idx_tensor)); + auto idx_ptr = idx_tensor->flat().data(); + + Tensor* frac_tensor = nullptr; + TensorShape frac_shape({ des_h, 3}); + OP_REQUIRES_OK(context, context->allocate_output(1, frac_shape, &frac_tensor)); + auto frac_ptr = frac_tensor->flat().data(); + + // calc bilinear xyz + Tensor buf_xyz; + TensorShape rst_shape({ des_h, 8}); + OP_REQUIRES_OK(context, context->allocate_temp(DT_UINT32, rst_shape, &buf_xyz)); + auto rst_xyz = buf_xyz.flat().data(); + bilinear_xyz_gpu(rst_xyz, frac_ptr, curr_depth_, des_xyz, target_depth_, des_h); + + Tensor buf_key; + xyz2key_gpu_op(context, &buf_key, rst_xyz, des_h * 8, target_depth_); + auto rst_key = buf_key.flat().data(); + + // binary search + search_key_gpu(idx_ptr, src_key, src_h, rst_key, des_h * 8); + } + + protected: + void xyz2key_gpu_op(OpKernelContext* context, Tensor* key_tensor, + const uint32* xyz, const int num, const int depth) { + OP_REQUIRES_OK(context, + context->allocate_temp(DT_UINT32, TensorShape({ num }), key_tensor)); + auto ptr = key_tensor->flat().data(); + xyz2key_gpu(ptr, xyz, num, depth); + } + + void key2xyz_gpu_op(OpKernelContext* context, Tensor* xyz_tensor, + const uint32* key, const int num, const int depth) { + OP_REQUIRES_OK(context, + context->allocate_temp(DT_UINT32, TensorShape({ num }), xyz_tensor)); + auto ptr = xyz_tensor->flat().data(); + key2xyz_gpu(ptr, key, num, depth); + } + + + private: + int curr_depth_; + int target_depth_; +}; + + +REGISTER_KERNEL_BUILDER(Name("OctreeBilinear").Device(DEVICE_GPU), OctreeBilinearOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/octree_conv_op.cc b/tensorflow/libs/octree_conv_op.cc new file mode 100644 index 0000000..24bbfa3 --- /dev/null +++ b/tensorflow/libs/octree_conv_op.cc @@ -0,0 +1,302 @@ +#include "octree_conv.h" +#include "octree_nn.h" +#include "tensorflow_gpu_gemm.h" + +#include +#include +#include +#include + +namespace tensorflow { +using octree::OctreeBaseConv; + +auto conv_forward_fun = [](::tensorflow::shape_inference::InferenceContext* c) { + int num_output; + TF_RETURN_IF_ERROR(c->GetAttr("num_output", &num_output)); + c->set_output(0, c->MakeShape({ 1, num_output, c->UnknownDim(), 1 })); + return Status::OK(); +}; + +auto conv_backward_fun = [](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->input(0)); + c->set_output(1, c->input(1)); + return Status::OK(); +}; + + +REGISTER_OP("OctreeConv") + .Input("btm_data: float") + .Input("weights: float") + .Input("octree: int8") + .Attr("depth: int") + .Attr("num_output: int") + .Attr("kernel_size: list(int)") + .Attr("stride: int") + .Output("top_data: float") + .SetShapeFn(conv_forward_fun) + .Doc(R"doc(Octree convolution operator.)doc"); + +REGISTER_OP("OctreeDeconv") + .Input("btm_data: float") + .Input("weights: float") + .Input("octree: int8") + .Attr("depth: int") + .Attr("num_output: int") + .Attr("kernel_size: list(int)") + .Attr("stride: int") + .Output("top_data: float") + .SetShapeFn(conv_forward_fun) + .Doc(R"doc(Octree deconvolution operator.)doc"); + +REGISTER_OP("OctreeConvGrad") + .Input("btm_data: float") + .Input("weights: float") + .Input("octree: int8") + .Input("top_diff: float") + .Attr("depth: int") + .Attr("num_output: int") + .Attr("kernel_size: list(int)") + .Attr("stride: int") + .Output("btm_diff: float") + .Output("weight_diff: float") + .SetShapeFn(conv_backward_fun) + .Doc(R"doc(Gradient for octree convolution operator.)doc"); + +REGISTER_OP("OctreeDeconvGrad") + .Input("btm_data: float") + .Input("weights: float") + .Input("octree: int8") + .Input("top_diff: float") + .Attr("depth: int") + .Attr("num_output: int") + .Attr("kernel_size: list(int)") + .Attr("stride: int") + .Output("btm_diff: float") + .Output("weight_diff: float") + .SetShapeFn(conv_backward_fun) + .Doc(R"doc(Gradient for octree convolution operator.)doc"); + + +class OctreeConvTF : public OpKernel, public OctreeBaseConv { + public: + explicit OctreeConvTF(OpKernelConstruction* context) + : OpKernel(context), OctreeBaseConv() { + OP_REQUIRES_OK(context, context->GetAttr("depth", &depth_)); + OP_REQUIRES_OK(context, context->GetAttr("num_output", &num_output_)); + OP_REQUIRES_OK(context, context->GetAttr("kernel_size", &kernel_size_)); + OP_REQUIRES_OK(context, context->GetAttr("stride", &stride_)); + resize_with_last_val(kernel_size_, 3); + + CHECK_GT(depth_, 0) << "The depth should be larger than 0"; + CHECK_GT(num_output_, 0) << "The num_output should be larger than 0"; + for (auto k : kernel_size_) { CHECK(0 < k && k < 4) << "Invalide kernel size"; } + CHECK(stride_ == 1 || stride_ == 2) << "Unsupport stride"; + } + + void setup_op(OpKernelContext* context) { + // setup gemm + tf_gemm_gpu_.set_context(context); + this->engine_gpu_ = &tf_gemm_gpu_; + + // setup octree + auto in_octree_ptr = context->input(2).flat().data(); + this->octree_.set_gpu(in_octree_ptr); + + // setup octree conv + const TensorShape& shape_in = context->input(0).shape(); + int channel_in = shape_in.dim_size(1), height_btm = shape_in.dim_size(2); + OctreeBaseConv::setup(kernel_size_, stride_, depth_, + channel_in, num_output_); + if (stride_ == 2 && is_deconvolution_layer()) { + CHECK_EQ(height_btm, this->octree_.info().node_num_nempty(depth_)); + } else { + CHECK_EQ(height_btm, this->octree_.info().node_num(depth_)) + << ", d: " << depth_ << ", channel_in: " << channel_in; + } + } + + void alloc_temp_memory(OpKernelContext* ctx, Tensor* workspace, + Tensor* data_buffer, Tensor* result_buffer, Tensor* ni_gpu) { + OctreeBaseConv::reshape(); + + int count = num_elements(this->workspace_shape_); + OP_REQUIRES_OK(ctx, + ctx->allocate_temp(DT_FLOAT, TensorShape({ count }), workspace)); + this->workspace_ = workspace->flat().data(); + + count = num_elements(this->result_buffer_shape_); + if (count != 0) { + OP_REQUIRES_OK(ctx, + ctx->allocate_temp(DT_FLOAT, TensorShape({ count }), result_buffer)); + this->result_buffer_ = result_buffer->flat().data(); + } else { + this->result_buffer_ = nullptr; + } + + count = num_elements(this->data_buffer_shape_); + if (count != 0) { + OP_REQUIRES_OK(ctx, + ctx->allocate_temp(DT_FLOAT, TensorShape({ count }), data_buffer)); + this->data_buffer_ = data_buffer->flat().data(); + } else { + this->data_buffer_ = nullptr; + } + + vector& ni_cpu = NeighHelper::get_ni(kernel_size_); + count = ni_cpu.size(); + if (count != 0) { + OP_REQUIRES_OK(ctx, + ctx->allocate_temp(DT_INT32, TensorShape({ count }), ni_gpu)); + auto ni_ptr = ni_gpu->flat().data(); + cudaMemcpy(ni_ptr, ni_cpu.data(), sizeof(int) * count, cudaMemcpyHostToDevice); + this->ni_gpu_ptr_ = ni_ptr; + } + } + + private: + int depth_; + int num_output_; + int stride_; + vector kernel_size_; + GEMMEngineTF tf_gemm_gpu_; +}; + + +class OctreeConvOp : public OctreeConvTF { + public: + explicit OctreeConvOp(OpKernelConstruction* context) + : OctreeConvTF(context) {} + + void Compute(OpKernelContext* context) override { + // init + this->setup_op(context); + Tensor workshape, data_buffer, rst_buffer, ni_gpu, *data_out; + this->alloc_temp_memory(context, &workshape, &data_buffer, &rst_buffer, &ni_gpu); + alloc_output_memory(context, &data_out); + + // get points + auto btm_data = context->input(0).flat().data(); + auto weights = context->input(1).flat().data(); + auto top_data = data_out->flat().data(); + + // forward + this->forward_gpu_gemm(top_data, btm_data, weights); + } + + virtual bool is_deconvolution_layer() override { return false; } + + void alloc_output_memory(OpKernelContext* context, Tensor** data_out) { + TensorShape tshape({ 1, this->top_shape_[1], this->top_shape_[2], 1 }); + OP_REQUIRES_OK(context, context->allocate_output(0, tshape, data_out)); + } +}; + + +class OctreeConvGradOp : public OctreeConvTF { + public: + explicit OctreeConvGradOp(OpKernelConstruction* context) + : OctreeConvTF(context) {} + + void Compute(OpKernelContext* context) override { + // init + this->setup_op(context); + Tensor workshape, data_buffer, rst_buffer, ni_gpu, *btm_out, *weights_out; + this->alloc_temp_memory(context, &workshape, &data_buffer, &rst_buffer, &ni_gpu); + alloc_output_memory(context, &btm_out, &weights_out); + + // get points + auto btm_data = context->input(0).flat().data(); + auto weights = context->input(1).flat().data(); + auto top_diff = context->input(3).flat().data(); + auto btm_diff = btm_out->flat().data(); + auto weights_diff = weights_out->flat().data(); + + // backward + this->weight_gpu_gemm(weights_diff, btm_data, top_diff); + this->backward_gpu_gemm(btm_diff, top_diff, weights); + } + + virtual bool is_deconvolution_layer() { return false; } + + void alloc_output_memory(OpKernelContext* context, + Tensor** btm_out, Tensor** weights_out) { + OP_REQUIRES_OK(context, + context->allocate_output(0, context->input(0).shape(), btm_out)); + OP_REQUIRES_OK(context, + context->allocate_output(1, context->input(1).shape(), weights_out)); + } +}; + + +class OctreeDeconvOp : public OctreeConvTF { + public: + explicit OctreeDeconvOp(OpKernelConstruction* context) + : OctreeConvTF(context) {} + + void Compute(OpKernelContext* context) override { + // init + this->setup_op(context); + Tensor workshape, data_buffer, rst_buffer, ni_gpu, *data_out; + this->alloc_temp_memory(context, &workshape, &data_buffer, &rst_buffer, &ni_gpu); + alloc_output_memory(context, &data_out); + + // get points + auto btm_data = context->input(0).flat().data(); + auto weights = context->input(1).flat().data(); + auto top_data = data_out->flat().data(); + + // forward + this->backward_gpu_gemm(top_data, btm_data, weights); + } + + virtual bool is_deconvolution_layer() override { return true; } + + void alloc_output_memory(OpKernelContext* context, Tensor** data_out) { + TensorShape tshape({ 1, this->top_shape_[1], this->top_shape_[2], 1 }); + OP_REQUIRES_OK(context, context->allocate_output(0, tshape, data_out)); + } +}; + + +class OctreeDeconvGradOp : public OctreeConvTF { + public: + explicit OctreeDeconvGradOp(OpKernelConstruction* context) + : OctreeConvTF(context) {} + + void Compute(OpKernelContext* context) override { + // init + this->setup_op(context); + Tensor workshape, data_buffer, rst_buffer, ni_gpu, *btm_out, *weights_out; + this->alloc_temp_memory(context, &workshape, &data_buffer, &rst_buffer, &ni_gpu); + alloc_output_memory(context, &btm_out, &weights_out); + + // get points + auto btm_data = context->input(0).flat().data(); + auto weights = context->input(1).flat().data(); + auto top_diff = context->input(3).flat().data(); + auto btm_diff = btm_out->flat().data(); + auto weights_diff = weights_out->flat().data(); + + // backward + this->weight_gpu_gemm(weights_diff, top_diff, btm_data); + this->forward_gpu_gemm(btm_diff, top_diff, weights); + } + + virtual bool is_deconvolution_layer() { return true; } + + void alloc_output_memory(OpKernelContext* context, + Tensor** btm_out, Tensor** weights_out) { + OP_REQUIRES_OK(context, + context->allocate_output(0, context->input(0).shape(), btm_out)); + OP_REQUIRES_OK(context, + context->allocate_output(1, context->input(1).shape(), weights_out)); + } +}; + + +REGISTER_KERNEL_BUILDER(Name("OctreeConv").Device(DEVICE_GPU), OctreeConvOp); +REGISTER_KERNEL_BUILDER(Name("OctreeDeconv").Device(DEVICE_GPU), OctreeDeconvOp); +REGISTER_KERNEL_BUILDER(Name("OctreeConvGrad").Device(DEVICE_GPU), OctreeConvGradOp); +REGISTER_KERNEL_BUILDER(Name("OctreeDeconvGrad").Device(DEVICE_GPU), OctreeDeconvGradOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/octree_gather_op.cc b/tensorflow/libs/octree_gather_op.cc new file mode 100644 index 0000000..56d84f0 --- /dev/null +++ b/tensorflow/libs/octree_gather_op.cc @@ -0,0 +1,104 @@ +#include "octree_nn.h" + +#include +#include +#include + +namespace tensorflow { + +auto gather_shape_fun = [](::tensorflow::shape_inference::InferenceContext* c) { + auto top_shape = c->input(0); + TF_RETURN_IF_ERROR(c->ReplaceDim(top_shape, 2, c->UnknownDim(), &top_shape)); + c->set_output(0, top_shape); + return Status::OK(); +}; + +REGISTER_OP("OctreeGather") + .Input("btm_data: float") // (1, C, Hb, 1) + .Input("index: int32") // (Ht,) + .Output("top_data: float") // (1, C, Ht, 1) + .SetShapeFn(gather_shape_fun) + .Doc(R"doc(Octree gather operator.)doc"); + +REGISTER_OP("OctreeGatherbk") + .Input("top_data: float") // (1, C, Ht, 1) + .Input("index: int32") // (Ht,) + .Input("btm_shape: int32") // (4,) + .Output("btm_data: float") // (1, C, Hb, 1) + .SetShapeFn(gather_shape_fun) + .Doc(R"doc(Octree gather backward operator.)doc"); + + +class OctreeGatherOp : public OpKernel { + public: + explicit OctreeGatherOp(OpKernelConstruction* context) + : OpKernel(context) {} + + void Compute(OpKernelContext* context) override { + // btm data + const Tensor& btm_tensor = context->input(0); + const TensorShape& btm_shape = btm_tensor.shape(); + auto btm_data = btm_tensor.flat().data(); + int channel = btm_shape.dim_size(1); + int btm_h = btm_shape.dim_size(2); + + // index data + const Tensor& idx_tensor = context->input(1); + auto idx = idx_tensor.flat().data(); + int top_h = idx_tensor.dim_size(0); + + // top data + TensorShape top_shape = btm_shape; + top_shape.set_dim(2, top_h); + Tensor* top_tensor = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, top_shape, &top_tensor)); + auto top_data = top_tensor->flat().data(); + + // gather data + octree_gather_gpu(top_data, top_h, channel, btm_data, btm_h, idx); + } +}; + + +class OctreeGatherbkOp : public OpKernel { + public: + explicit OctreeGatherbkOp(OpKernelConstruction* context) + : OpKernel(context) {} + + void Compute(OpKernelContext* context) override { + // top grad + const Tensor& top_tensor = context->input(0); + const TensorShape& top_shape = top_tensor.shape(); + auto top_data = top_tensor.flat().data(); + int channel = top_shape.dim_size(1); + int top_h = top_shape.dim_size(2); + + // index data + const Tensor& idx_tensor = context->input(1); + auto idx = idx_tensor.flat().data(); + CHECK_EQ(top_h, idx_tensor.dim_size(0)); + + // shape + const Tensor& shape_tensor = context->input(2); + auto shape_data = shape_tensor.flat().data(); + int btm_h = shape_data[2]; + CHECK(shape_tensor.NumElements() == 4 && shape_data[0] == 1 && + shape_data[1] == channel && shape_data[3] == 1); + + // btm grad + TensorShape btm_shape = top_shape; + btm_shape.set_dim(2, btm_h); + Tensor* btm_tensor = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, btm_shape, &btm_tensor)); + auto btm_data = btm_tensor->flat().data(); + + // padding data + octree_gatherbk_gpu(top_data, top_h, channel, btm_data, btm_h, idx); + } +}; + + +REGISTER_KERNEL_BUILDER(Name("OctreeGather").Device(DEVICE_GPU), OctreeGatherOp); +REGISTER_KERNEL_BUILDER(Name("OctreeGatherbk").Device(DEVICE_GPU).HostMemory("btm_shape"), OctreeGatherbkOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/octree_grow_op.cc b/tensorflow/libs/octree_grow_op.cc new file mode 100644 index 0000000..168075a --- /dev/null +++ b/tensorflow/libs/octree_grow_op.cc @@ -0,0 +1,140 @@ +#include "octree_nn.h" +#include "octree_parser.h" + +#include +#include +#include +#include + +namespace tensorflow { + +REGISTER_OP("OctreeGrow") + .Input("in_octree: int8") + .Attr("target_depth: int") + .Attr("full_octree: bool = false") + .Output("out_octree: int8") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->MakeShape({ c->UnknownDim() })); + return Status::OK(); + }) + .Doc(R"doc(Octree grow operator.)doc"); + + +class OctreeGrowOp : public OpKernel { + public: + explicit OctreeGrowOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("target_depth", &target_depth_)); + OP_REQUIRES_OK(context, context->GetAttr("full_octree", &full_octree_)); + } + + void Compute(OpKernelContext* context) override { + // in octree + OctreeParser octree_in; + octree_in.set_gpu(context->input(0).flat().data()); + + // out info + batch_size_ = octree_in.info().batch_size(); + node_num_ = octree_in.info().node_num_nempty(target_depth_ - 1) << 3; + OctreeInfo oct_info_; + oct_info_ = octree_in.info(); + update_octreeinfo(oct_info_); + + // out octree + Tensor* tensor_out = nullptr; + TensorShape shape_out({ oct_info_.sizeof_octree() }); + OP_REQUIRES_OK(context, context->allocate_output(0, shape_out, &tensor_out)); + auto* ptr_out = tensor_out->flat().data(); + // memset_gpu(tensor_out->NumElements(), 0, ptr_out); + cudaMemset(ptr_out, 0, tensor_out->NumElements()); + + // copy octree + OctreeParser octree_out; + octree_out.set_gpu(ptr_out, &oct_info_); + copy_octree_gpu(octree_out, octree_in); + + // grow octree + if (full_octree_) { + calc_neigh_gpu(octree_out.mutable_neighbor_gpu(target_depth_), + target_depth_, batch_size_); + generate_key_gpu(octree_out.mutable_key_gpu(target_depth_), + target_depth_, batch_size_); + sequence_gpu(octree_out.mutable_children_gpu(target_depth_), node_num_); + } else { + Tensor displacement, parent; + init_neigh_ptrs(context, parent, displacement); + const int* label_ptr = octree_out.children_gpu(target_depth_ - 1); + calc_neigh_gpu(octree_out.mutable_neighbor_gpu(target_depth_), + octree_out.neighbor_gpu(target_depth_ - 1), label_ptr, + octree_out.info().node_num(target_depth_ - 1), ptr_parent_, ptr_dis_); + generate_key_gpu(octree_out.mutable_key_gpu(target_depth_), + octree_out.key_gpu(target_depth_ - 1), label_ptr, + octree_out.info().node_num(target_depth_ - 1)); + sequence_gpu(octree_out.mutable_children_gpu(target_depth_), node_num_); + } + } + + private: + void update_octreeinfo(OctreeInfo& oct_info_) { + oct_info_.set_depth(target_depth_); + if (full_octree_) { + oct_info_.set_full_layer(target_depth_); + } + float width = 1 << target_depth_; + float bbmin[] = { 0, 0, 0 }; + float bbmax[] = { width, width, width }; + oct_info_.set_bbox(bbmin, bbmax); + oct_info_.set_nnum(target_depth_, node_num_); + // Just set the non-empty node number as node_num_, + // it needs to be updated by the new node-splitting label + oct_info_.set_nempty(target_depth_, node_num_); + oct_info_.set_nnum_cum(); + oct_info_.set_ptr_dis(); + } + + // todo: replace the cudaMemcpy with the wrapper function memcpy_gpu + + void copy_octree_gpu(OctreeParser& octree_out, const OctreeParser& octree_in) { + int node_num_cum = octree_in.info().node_num_cum(target_depth_); + int key_channel = octree_in.info().channel(OctreeInfo::kKey); + int child_channel = octree_in.info().channel(OctreeInfo::kChild); + int neigh_channel = octree_in.info().channel(OctreeInfo::kNeigh); + int feature_channel = octree_in.info().channel(OctreeInfo::kFeature); + cudaMemcpy(octree_out.mutable_key_gpu(0), octree_in.key_gpu(0), + key_channel * node_num_cum * sizeof(int), cudaMemcpyDeviceToDevice); + cudaMemcpy(octree_out.mutable_children_gpu(0), octree_in.children_gpu(0), + child_channel * node_num_cum * sizeof(int), cudaMemcpyDeviceToDevice); + cudaMemcpy(octree_out.mutable_neighbor_gpu(0), octree_in.neighbor_gpu(0), + neigh_channel * node_num_cum * sizeof(int), cudaMemcpyDeviceToDevice); + cudaMemcpy(octree_out.mutable_feature_gpu(0), octree_in.feature_gpu(0), + feature_channel * node_num_cum * sizeof(float), cudaMemcpyDeviceToDevice); + } + + void init_neigh_ptrs(OpKernelContext* ctx, Tensor& parent, Tensor& dis) { + const vector& dis_cpu = NeighHelper::Get().get_dis_array(); + TensorShape dshape({ (long long int) dis_cpu.size() }); + OP_REQUIRES_OK(ctx, ctx->allocate_temp(DT_INT32, dshape, &dis)); + ptr_dis_ = dis.flat().data(); + cudaMemcpy(ptr_dis_, dis_cpu.data(), dis_cpu.size() * sizeof(int), + cudaMemcpyHostToDevice); + + const vector& parent_cpu = NeighHelper::Get().get_parent_array(); + TensorShape pshape({ (long long int) parent_cpu.size() }); + OP_REQUIRES_OK(ctx, ctx->allocate_temp(DT_INT32, pshape, &parent)); + ptr_parent_ = parent.flat().data(); + cudaMemcpy(ptr_parent_, parent_cpu.data(), parent_cpu.size() * sizeof(int), + cudaMemcpyHostToDevice); + } + + private: + int batch_size_; + int target_depth_; + int node_num_; + bool full_octree_; + int* ptr_parent_; + int* ptr_dis_; +}; + + +REGISTER_KERNEL_BUILDER(Name("OctreeGrow").Device(DEVICE_GPU), OctreeGrowOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/octree_key_op.cc b/tensorflow/libs/octree_key_op.cc new file mode 100644 index 0000000..53e17e0 --- /dev/null +++ b/tensorflow/libs/octree_key_op.cc @@ -0,0 +1,230 @@ +#include "octree_nn.h" +#include "octree_parser.h" + +#include +#include +#include +#include + +namespace tensorflow { + +REGISTER_OP("OctreeEncodeKey") + .Input("xyz: uint8") + .Output("key: uint32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->MakeShape({c->Dim(c->input(0), 0)})); + return Status::OK(); + }) + .Doc(R"doc(Encode the (x, y, z, id) to key in uint32)")doc"); + +REGISTER_OP("OctreeDecodeKey") + .Input("key: uint32") + .Output("xyz: uint8") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->MakeShape({c->Dim(c->input(0), 0), 4})); + return Status::OK(); + }) + .Doc(R"doc(Decode the key to (x, y, z, id) in uint8)")doc"); + +REGISTER_OP("OctreeKeyToXyz") + .Input("key: uint32") + .Attr("depth: int = 8") + .Output("xyz: uint32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->input(0)); + return Status::OK(); + }) + .Doc(R"doc(Convert the key to xyz)")doc"); + +REGISTER_OP("OctreeXyzToKey") + .Input("xyz: uint32") + .Attr("depth: int = 8") + .Output("key: uint32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->input(0)); + return Status::OK(); + }) + .Doc(R"doc(Convert the xyz to key)")doc"); + +REGISTER_OP("OctreeSearchKey") + .Input("key: uint32") + .Input("octree: int8") + .Attr("depth: int") + .Attr("is_xyz: bool = True") + .Output("kidx: int32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->MakeShape({ c->UnknownDim() })); + return Status::OK(); + }) + .Doc(R"doc(Octree search operator.)doc"); + + +class OctreeEncodeKeyOp : public OpKernel { + public: + explicit OctreeEncodeKeyOp(OpKernelConstruction* context) + : OpKernel(context) {} + + void Compute(OpKernelContext* context) override { + // in data + const Tensor& data_in = context->input(0); + auto ptr_in = data_in.flat().data(); + int num = data_in.dim_size(0); + int channel = data_in.dim_size(1); + CHECK_EQ(data_in.dims(), 2) << "The dim of input tensor must be 2."; + CHECK_EQ(channel, 4) << "The channel of input tensor must be 4."; + + // out data + Tensor* data_out = nullptr; + TensorShape shape_out({ num }); + OP_REQUIRES_OK(context, context->allocate_output(0, shape_out, &data_out)); + auto ptr_out = data_out->flat().data(); + + // copy data + cudaMemcpy(ptr_out, ptr_in, sizeof(uint8) * channel * num, cudaMemcpyDeviceToDevice); + } +}; + +class OctreeDecodeKeyOp : public OpKernel { + public: + explicit OctreeDecodeKeyOp(OpKernelConstruction* context) + : OpKernel(context) {} + + void Compute(OpKernelContext* context) override { + // in data + const Tensor& data_in = context->input(0); + auto ptr_in = data_in.flat().data(); + int num = data_in.dim_size(0); + CHECK_EQ(data_in.dims(), 1) << "The dim of input tensor must be 1."; + + // out data + Tensor* data_out = nullptr; + TensorShape shape_out({num, 4}); + OP_REQUIRES_OK(context, context->allocate_output(0, shape_out, &data_out)); + auto ptr_out = data_out->flat().data(); + + // copy data + cudaMemcpy(ptr_out, ptr_in, sizeof(uint32) * num, cudaMemcpyDeviceToDevice); + } +}; + +class OctreeKeyToXyzOp : public OpKernel { + public: + explicit OctreeKeyToXyzOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("depth", &depth_)); + } + + void Compute(OpKernelContext* context) override { + // in data + const Tensor& data_in = context->input(0); + const TensorShape& shape_in = data_in.shape(); + auto ptr_in = data_in.flat().data(); + int num = shape_in.num_elements(); + CHECK_GE(num, 1) << "The element number of input tensor must be 1."; + + // out data + Tensor* data_out = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, shape_in, &data_out)); + auto ptr_out = data_out->flat().data(); + + // convert + key2xyz_gpu(ptr_out, ptr_in, num, depth_); + } + + private: + int depth_; +}; + +class OctreeXyzToKeyOp : public OpKernel { + public: + explicit OctreeXyzToKeyOp(OpKernelConstruction* context) + : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("depth", &depth_)); + } + + void Compute(OpKernelContext* context) override { + // in data + const Tensor& data_in = context->input(0); + const TensorShape& shape_in = data_in.shape(); + auto ptr_in = data_in.flat().data(); + int num = shape_in.num_elements(); + CHECK_GE(num, 1) << "The element number of input tensor must be 1."; + + // out data + Tensor* data_out = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, shape_in, &data_out)); + auto ptr_out = data_out->flat().data(); + + // convert + xyz2key_gpu(ptr_out, ptr_in, num, depth_); + } + + private: + int depth_; +}; + +class OctreeSearchKeyOp : public OpKernel { + public: + explicit OctreeSearchKeyOp(OpKernelConstruction* context) + : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("depth", &depth_)); + OP_REQUIRES_OK(context, context->GetAttr("is_xyz", &is_xyz_)); + } + + void Compute(OpKernelContext* context) override { + // input + const Tensor& data_in = context->input(0); + const TensorShape& shape_in = data_in.shape(); + const uint32* src_key = data_in.flat().data(); + int src_h = shape_in.num_elements(); + CHECK_GE(src_h, 1) << "The element number of input tensor must be 1."; + + // xyz2key + Tensor src_key_tensor; + if (is_xyz_) { + xyz2key_gpu_op(context, &src_key_tensor, src_key, src_h, depth_); + src_key = src_key_tensor.flat().data(); + } + + // octree + OctreeParser octree_; + octree_.set_gpu(context->input(1).flat().data()); + int des_h = octree_.info().node_num(depth_); + const uint32* des_key = octree_.key_gpu(depth_); + Tensor des_key_tensor; + if (octree_.info().is_key2xyz()) { + xyz2key_gpu_op(context, &des_key_tensor, des_key, des_h, depth_); + des_key = des_key_tensor.flat().data(); + } + + // output + Tensor* des_tensor = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, shape_in, &des_tensor)); + auto idx_ptr = des_tensor->flat().data(); + + // binary search + search_key_gpu(idx_ptr, des_key, des_h, src_key, src_h); + } + + protected: + // todo: isolated the following functions out of this file + void xyz2key_gpu_op(OpKernelContext* context, Tensor* key_tensor, + const uint32* xyz, const int num, const int depth) { + OP_REQUIRES_OK(context, + context->allocate_temp(DT_UINT32, TensorShape({ num }), key_tensor)); + auto ptr = key_tensor->flat().data(); + xyz2key_gpu(ptr, xyz, num, depth); + } + + private: + int depth_; + bool is_xyz_; +}; + + +REGISTER_KERNEL_BUILDER(Name("OctreeDecodeKey").Device(DEVICE_GPU), OctreeDecodeKeyOp); +REGISTER_KERNEL_BUILDER(Name("OctreeEncodeKey").Device(DEVICE_GPU), OctreeEncodeKeyOp); +REGISTER_KERNEL_BUILDER(Name("OctreeKeyToXyz").Device(DEVICE_GPU), OctreeKeyToXyzOp); +REGISTER_KERNEL_BUILDER(Name("OctreeXyzToKey").Device(DEVICE_GPU), OctreeXyzToKeyOp); +REGISTER_KERNEL_BUILDER(Name("OctreeSearchKey").Device(DEVICE_GPU), OctreeSearchKeyOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/octree_mask_op.cc b/tensorflow/libs/octree_mask_op.cc new file mode 100644 index 0000000..f6ee43e --- /dev/null +++ b/tensorflow/libs/octree_mask_op.cc @@ -0,0 +1,59 @@ +#include "octree_nn.h" + +#include +#include +#include +#include + +namespace tensorflow { + +REGISTER_OP("OctreeMask") + .Input("in_data: float") + .Input("in_label: int32") + .Attr("mask: int") + .Output("out_data: float") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->input(0)); + return Status::OK(); + }) + .Doc(R"doc(Octree mask operator.)doc"); + + +class OctreeMaskOp : public OpKernel { + public: + explicit OctreeMaskOp(OpKernelConstruction* context) + : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("mask", &mask_)); + } + + void Compute(OpKernelContext* context) override { + // in data + const Tensor& in_data = context->input(0); + const TensorShape in_shape = in_data.shape(); + auto in_ptr = in_data.flat().data(); + + // in label + const Tensor& in_label = context->input(1); + auto label_ptr = in_label.flat().data(); + CHECK_EQ(in_shape.dim_size(2), in_label.NumElements()); + + // out data + Tensor* out_tensor = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, in_shape, &out_tensor)); + auto out_ptr = out_tensor->flat().data(); + + // exec + int height = in_shape.dim_size(2); + int channel = in_shape.dim_size(1); + int num = channel * height; + octree_mask_gpu(out_ptr, in_ptr, label_ptr, height, mask_, num); + } + + private: + int mask_; +}; + + +REGISTER_KERNEL_BUILDER(Name("OctreeMask").Device(DEVICE_GPU), OctreeMaskOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/octree_max_pool_op.cc b/tensorflow/libs/octree_max_pool_op.cc new file mode 100644 index 0000000..300bb97 --- /dev/null +++ b/tensorflow/libs/octree_max_pool_op.cc @@ -0,0 +1,195 @@ +#include "octree_nn.h" +#include "octree_parser.h" + +#include +#include +#include +#include + + +namespace tensorflow { + +auto pool_shape_fun = [](::tensorflow::shape_inference::InferenceContext* c) { + auto top_shape = c->input(0); + TF_RETURN_IF_ERROR(c->ReplaceDim(top_shape, 2, c->UnknownDim(), &top_shape)); + c->set_output(0, top_shape); + return Status::OK(); +}; + +auto pool_shape_fun2 = [](::tensorflow::shape_inference::InferenceContext* c) { + auto top_shape = c->input(0); + TF_RETURN_IF_ERROR(c->ReplaceDim(top_shape, 2, c->UnknownDim(), &top_shape)); + c->set_output(0, top_shape); + c->set_output(1, top_shape); + return Status::OK(); +}; + + +REGISTER_OP("OctreeMaxPool") + .Input("btm_data: float") + .Input("octree: int8") + .Attr("depth: int") + .Output("top_data: float") + .Output("mask: int32") + .SetShapeFn(pool_shape_fun2) + .Doc(R"doc(Octree max pooling operator.)doc"); + +REGISTER_OP("OctreeMaxUnpool") + .Input("top_data: float") + .Input("mask: int32") + .Input("octree: int8") + .Attr("depth: int") + .Output("btm_data: float") + .SetShapeFn(pool_shape_fun) + .Doc(R"doc(Octree max unpooling operator.)doc"); + +REGISTER_OP("OctreeMaskPool") + .Input("btm_data: float") + .Input("mask: int32") + .Input("octree: int8") + .Attr("depth: int") + .Output("top_data: float") + .SetShapeFn(pool_shape_fun) + .Doc(R"doc(Octree mask pooling operator.)doc"); + + +class OctreePoolBase : public OpKernel { + public: + explicit OctreePoolBase(OpKernelConstruction* context) + : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("depth", &depth_)); + CHECK_GT(depth_, 1) << "Depth should be larger than 1"; + } + + void set_octree_parser(OpKernelContext* context, int idx, OctreeParser& octree_) { + auto octree_ptr = context->input(idx).flat().data(); + octree_.set_gpu(octree_ptr); + } + + protected: + int depth_; +}; + +class OctreeMaxPoolOp : public OctreePoolBase { + public: + explicit OctreeMaxPoolOp(OpKernelConstruction* context) + : OctreePoolBase(context) {} + + void Compute(OpKernelContext* context) override { + // in octree + OctreeParser octree_; + this->set_octree_parser(context, 1, octree_); + + // btm data + const Tensor& btm_data = context->input(0); + const TensorShape& btm_shape = btm_data.shape(); + auto btm_ptr = btm_data.flat().data(); + int channel = btm_shape.dim_size(1); + int btm_h = btm_shape.dim_size(2); + + // check + int btm_depth = this->depth_; + CHECK_EQ(octree_.info().node_num(btm_depth), btm_h); + + // top data + TensorShape top_shape = btm_shape; + int top_h = btm_h >> 3; + top_shape.set_dim(2, top_h); + Tensor* top_data = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, top_shape, &top_data)); + auto top_ptr = top_data->flat().data(); + + // mask + Tensor* mask = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(1, top_shape, &mask)); + auto mask_ptr = mask->flat().data(); + + // pooling + octree_max_pool_gpu(top_ptr, top_h, mask_ptr, btm_ptr, btm_h, channel); + } +}; + +class OctreeMaxUnpoolOp : public OctreePoolBase { + public: + explicit OctreeMaxUnpoolOp(OpKernelConstruction* context) + : OctreePoolBase(context) {} + + void Compute(OpKernelContext* context) override { + // in octree + OctreeParser octree_; + this->set_octree_parser(context, 2, octree_); + + // top data + const Tensor& top_data = context->input(0); + const TensorShape& top_shape = top_data.shape(); + auto top_ptr = top_data.flat().data(); + int channel = top_shape.dim_size(1); + int top_h = top_shape.dim_size(2); + + // mask + const Tensor& mask = context->input(1); + const TensorShape& mask_shape = mask.shape(); + auto mask_ptr = mask.flat().data(); + + // check + int btm_depth = this->depth_; + CHECK(mask_shape == top_shape); + CHECK_EQ(top_h, octree_.info().node_num_nempty(btm_depth - 1)); + + // top data + TensorShape btm_shape = top_shape; + int btm_h = top_h << 3; + btm_shape.set_dim(2, btm_h); + Tensor* btm_data = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, btm_shape, &btm_data)); + auto btm_ptr = btm_data->flat().data(); + + // pooling + octree_max_unpool_gpu(top_ptr, top_h, mask_ptr, btm_ptr, btm_h, channel); + } +}; + +class OctreeMaskPoolOp : public OctreePoolBase { + public: + explicit OctreeMaskPoolOp(OpKernelConstruction* context) + : OctreePoolBase(context) {} + + void Compute(OpKernelContext* context) override { + // in octree + OctreeParser octree_; + this->set_octree_parser(context, 2, octree_); + + // btm data + const Tensor& btm_data = context->input(0); + const TensorShape& btm_shape = btm_data.shape(); + auto btm_ptr = btm_data.flat().data(); + int channel = btm_shape.dim_size(1); + int btm_h = btm_shape.dim_size(2); + + // mask + const Tensor& mask = context->input(1); + const TensorShape& top_shape = mask.shape(); + auto mask_ptr = mask.flat().data(); + int top_h = top_shape.dim_size(2); + + // check + int btm_depth = this->depth_; + CHECK_EQ(octree_.info().node_num(btm_depth), btm_h); + CHECK_EQ(top_h, btm_h >> 3); + + // top data + Tensor* top_data = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, top_shape, &top_data)); + auto top_ptr = top_data->flat().data(); + + // pooling + octree_mask_pool_gpu(top_ptr, top_h, mask_ptr, btm_ptr, btm_h, channel); + } +}; + + +REGISTER_KERNEL_BUILDER(Name("OctreeMaxPool").Device(DEVICE_GPU), OctreeMaxPoolOp); +REGISTER_KERNEL_BUILDER(Name("OctreeMaxUnpool").Device(DEVICE_GPU), OctreeMaxUnpoolOp); +REGISTER_KERNEL_BUILDER(Name("OctreeMaskPool").Device(DEVICE_GPU), OctreeMaskPoolOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/octree_new_op.cc b/tensorflow/libs/octree_new_op.cc new file mode 100644 index 0000000..e025f7a --- /dev/null +++ b/tensorflow/libs/octree_new_op.cc @@ -0,0 +1,85 @@ +#include +#include +#include +#include + +#include "octree_nn.h" +#include "octree_parser.h" + +namespace tensorflow { + +REGISTER_OP("OctreeNew") + .Attr("batch_size: int = 1") + .Attr("adaptive_layer: int = 0") + .Attr("channel: int = 3") + .Attr("has_displace: bool = false") + .Output("out_octree: int8") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->MakeShape({c->UnknownDim()})); + return Status::OK(); + }) + .Doc(R"doc(Octree new operator.)doc"); + +class OctreeNewOp : public OpKernel { + public: + explicit OctreeNewOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("batch_size", &batch_size_)); + OP_REQUIRES_OK(context, context->GetAttr("adaptive_layer", &adaptive_)); + OP_REQUIRES_OK(context, context->GetAttr("has_displace", &has_displace_)); + OP_REQUIRES_OK(context, context->GetAttr("channel", &channel_)); + } + + void Compute(OpKernelContext* context) override { + CHECK_GE(batch_size_, 1); + int node_num_ = batch_size_; + int depth_ = 0; + + // octree info + OctreeInfo oct_info_; + oct_info_.set_batch_size(batch_size_); + oct_info_.set_depth(depth_); + oct_info_.set_full_layer(depth_); + oct_info_.set_node_dis(has_displace_); + if (adaptive_ > 1) { + oct_info_.set_adaptive(true); + oct_info_.set_adaptive_layer(adaptive_); + } else { + oct_info_.set_adaptive(false); + } + oct_info_.set_key2xyz(true); + oct_info_.set_property(OctreeInfo::kKey, 2, -1); + oct_info_.set_property(OctreeInfo::kChild, 1, -1); + oct_info_.set_property(OctreeInfo::kNeigh, 8, -1); + oct_info_.set_property(OctreeInfo::kFeature, channel_, -1); + float bbmin[] = {0, 0, 0}; + float bbmax[] = {2, 2, 2}; + oct_info_.set_bbox(bbmin, bbmax); + oct_info_.set_nnum(depth_, node_num_); + oct_info_.set_nnum_cum(); + oct_info_.set_nempty(depth_, node_num_); + oct_info_.set_ptr_dis(); + + // out octree + Tensor* tensor_out = nullptr; + TensorShape shape_out({oct_info_.sizeof_octree()}); + OP_REQUIRES_OK(context, context->allocate_output(0, shape_out, &tensor_out)); + auto* ptr_out = tensor_out->flat().data(); + cudaMemset(ptr_out, 0, tensor_out->NumElements()); + + // set octree, skip the propoerties neigh and feature + OctreeParser octree_out; + octree_out.set_gpu(ptr_out, &oct_info_); + sequence_gpu(octree_out.mutable_key_gpu(depth_), node_num_); + sequence_gpu(octree_out.mutable_children_gpu(depth_), node_num_); + } + + private: + int batch_size_; + int adaptive_; + int channel_; + bool has_displace_; +}; + +REGISTER_KERNEL_BUILDER(Name("OctreeNew").Device(DEVICE_GPU), OctreeNewOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/octree_pad_op.cc b/tensorflow/libs/octree_pad_op.cc new file mode 100644 index 0000000..259e399 --- /dev/null +++ b/tensorflow/libs/octree_pad_op.cc @@ -0,0 +1,135 @@ +#include "octree_parser.h" +#include "octree_nn.h" + +#include +#include +#include + +namespace tensorflow { + +auto pad_shape_fun = [](::tensorflow::shape_inference::InferenceContext* c) { + auto top_shape = c->input(0); + TF_RETURN_IF_ERROR(c->ReplaceDim(top_shape, 2, c->UnknownDim(), &top_shape)); + c->set_output(0, top_shape); + return Status::OK(); +}; + +REGISTER_OP("OctreePad") + .Input("btm_data: float") + .Input("octree: int8") + .Attr("depth: int") + .Attr("dval: float = 0.0") + .Output("top_data: float") + .SetShapeFn(pad_shape_fun) + .Doc(R"doc(Octree padding operator.)doc"); + +REGISTER_OP("OctreeDepad") + .Input("top_data: float") + .Input("octree: int8") + .Attr("depth: int") + .Output("btm_data: float") + .SetShapeFn(pad_shape_fun) + .Doc(R"doc(Octree depadding operator.)doc"); + + +class OctreePadBase : public OpKernel { + public: + explicit OctreePadBase(OpKernelConstruction* context) + : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("depth", &depth_)); + CHECK_GE(depth_, 1) << "Depth should be larger than 1"; + } + + void set_octree_parser(OpKernelContext* context, OctreeParser& octree_) { + auto octree_ptr = context->input(1).flat().data(); + octree_.set_gpu(octree_ptr); + } + + protected: + int depth_; +}; + + +class OctreePadOp : public OctreePadBase { + public: + explicit OctreePadOp(OpKernelConstruction* context) + : OctreePadBase(context) { + OP_REQUIRES_OK(context, context->GetAttr("dval", &dval_)); + } + + void Compute(OpKernelContext* context) override { + // in octree + OctreeParser octree_; + this->set_octree_parser(context, octree_); + + // btm data + const Tensor& btm_data = context->input(0); + const TensorShape& btm_shape = btm_data.shape(); + auto btm_ptr = btm_data.flat().data(); + int channel = btm_shape.dim_size(1); + int btm_h = btm_shape.dim_size(2); + + // check + int depth = this->depth_; + CHECK_EQ(octree_.info().node_num_nempty(depth), btm_h) + << ", pad, d = " << depth << ", channel = " << channel; + + // top data + TensorShape top_shape = btm_shape; + int top_h = octree_.info().node_num(depth); + top_shape.set_dim(2, top_h); + Tensor* top_data = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, top_shape, &top_data)); + auto top_ptr = top_data->flat().data(); + + // padding data + pad_forward_gpu(top_ptr, top_h, channel, + btm_ptr, btm_h, octree_.children_gpu(depth), dval_); + } + + protected: + float dval_; +}; + + +class OctreeDepadOp : public OctreePadBase { + public: + explicit OctreeDepadOp(OpKernelConstruction* context) + : OctreePadBase(context) {} + + void Compute(OpKernelContext* context) override { + // in octree + OctreeParser octree_; + this->set_octree_parser(context, octree_); + + // top grad + const Tensor& top_data = context->input(0); + const TensorShape& top_shape = top_data.shape(); + auto top_ptr = top_data.flat().data(); + int channel = top_shape.dim_size(1); + int top_h = top_shape.dim_size(2); + + // check + int depth = this->depth_; + CHECK_EQ(octree_.info().node_num(depth), top_h) + << ", depad, d = " << depth << ", channel = " << channel; + + // btm grad + TensorShape btm_shape = top_shape; + int btm_h = octree_.info().node_num_nempty(depth); + btm_shape.set_dim(2, btm_h); + Tensor* btm_data = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, btm_shape, &btm_data)); + auto btm_ptr = btm_data->flat().data(); + + // padding data + pad_backward_gpu(btm_ptr, btm_h, channel, + top_ptr, top_h, octree_.children_gpu(depth)); + } +}; + + +REGISTER_KERNEL_BUILDER(Name("OctreePad").Device(DEVICE_GPU), OctreePadOp); +REGISTER_KERNEL_BUILDER(Name("OctreeDepad").Device(DEVICE_GPU), OctreeDepadOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/octree_property_op.cc b/tensorflow/libs/octree_property_op.cc new file mode 100644 index 0000000..123dd56 --- /dev/null +++ b/tensorflow/libs/octree_property_op.cc @@ -0,0 +1,138 @@ +#include "octree_nn.h" +#include "octree_parser.h" + +#include +#include +#include +#include + +namespace tensorflow { + +REGISTER_OP("OctreeProperty") + .Input("octree: int8") + .Attr("property_name: string") + .Attr("depth: int") + .Attr("channel: int") + .Attr("dtype: {int32,float32,uint32}") + .Output("out_property: dtype") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + int channel; + TF_RETURN_IF_ERROR(c->GetAttr("channel", &channel)); + c->set_output(0, c->MakeShape({channel, c->UnknownDim()})); + return Status::OK(); + }) + .Doc(R"doc(Octree property operator.)doc"); + + +class OctreePropertyOp : public OpKernel { + public: + explicit OctreePropertyOp(OpKernelConstruction* context) : + OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("property_name", &property_name_)); + OP_REQUIRES_OK(context, context->GetAttr("depth", &depth_)); + OP_REQUIRES_OK(context, context->GetAttr("channel", &channel_)); + OP_REQUIRES_OK(context, context->GetAttr("dtype", &dtype_)); + } + + void Compute(OpKernelContext* context) override { + auto octree_ptr = context->input(0).flat().data(); + OctreeParser octree_; + octree_.set_gpu(octree_ptr); + + Tensor buf0, buf1; + const void* property_ptr = nullptr; + int length = octree_.info().node_num(depth_), channel = 1; + if (property_name_ == "key") { + property_ptr = octree_.key_gpu(depth_); + channel = octree_.info().channel(OctreeInfo::kKey); + CHECK_EQ(dtype_, DataType::DT_UINT32); + } else if (property_name_ == "xyz") { + // key2xzy + property_ptr = octree_.key_gpu(depth_); + channel = octree_.info().channel(OctreeInfo::kKey); + if (!octree_.info().is_key2xyz()) { + OP_REQUIRES_OK(context, + context->allocate_temp(DT_UINT32, TensorShape({ length }), &buf0)); + uint32* ptr = buf0.flat().data(); + key2xyz_gpu(ptr, (const uint32*)property_ptr, length, depth_); + property_ptr = ptr; + } + CHECK_EQ(dtype_, DataType::DT_UINT32); + //// xyz to float + //channel = channel_; + //OP_REQUIRES_OK(context, + // context->allocate_temp(DT_FLOAT, TensorShape({ length * channel }), &buf1)); + //CHECK_EQ(dtype_, DataType::DT_FLOAT); + //float* ptr = buf1.flat().data(); + //xyz2coord_gpu(ptr, xyz, length, channel); + //property_ptr = ptr; + } else if (property_name_ == "index") { + const unsigned int* key_ptr = octree_.key_gpu(depth_); + channel = octree_.info().channel(OctreeInfo::kKey); + OP_REQUIRES_OK(context, + context->allocate_temp(DT_INT32, TensorShape({ length }), &buf0)); + int* idx_ptr = buf0.flat().data(); + key2idx_gpu(idx_ptr, key_ptr, length); + property_ptr = idx_ptr; + CHECK_EQ(dtype_, DataType::DT_INT32); + } else if (property_name_ == "child") { + property_ptr = octree_.children_gpu(depth_); + channel = octree_.info().channel(OctreeInfo::kChild); + CHECK_EQ(dtype_, DataType::DT_INT32); + } else if (property_name_ == "neigh") { + property_ptr = octree_.neighbor_gpu(depth_); + channel = octree_.info().channel(OctreeInfo::kNeigh); + CHECK_EQ(dtype_, DataType::DT_INT32); + } else if (property_name_ == "feature") { + property_ptr = octree_.feature_gpu(depth_); + channel = octree_.info().channel(OctreeInfo::kFeature); + CHECK_EQ(dtype_, DataType::DT_FLOAT); + } else if (property_name_ == "label") { + property_ptr = octree_.label_gpu(depth_); + channel = octree_.info().channel(OctreeInfo::kLabel); + CHECK_EQ(dtype_, DataType::DT_FLOAT); + } else if (property_name_ == "split") { + property_ptr = octree_.split_gpu(depth_); + channel = octree_.info().channel(OctreeInfo::kSplit); + CHECK_EQ(dtype_, DataType::DT_FLOAT); + } else { + LOG(FATAL) << "Unsupported Octree Property"; + } + CHECK_EQ(channel_, channel) << "The specified channel_ is wrong."; + + Tensor* out_tensor; + TensorShape out_shape({ channel, length }); + OP_REQUIRES_OK(context, context->allocate_output(0, out_shape, &out_tensor)); + + int num = channel * length; + switch (dtype_) { + case DataType::DT_UINT32: { + auto ptr = out_tensor->flat().data(); + cudaMemcpy(ptr, property_ptr, sizeof(uint32) * num, cudaMemcpyDeviceToDevice); + } + break; + case DataType::DT_INT32: { + auto ptr = out_tensor->flat().data(); + cudaMemcpy(ptr, property_ptr, sizeof(int) * num, cudaMemcpyDeviceToDevice); + } + break; + case DataType::DT_FLOAT: { + auto ptr = out_tensor->flat().data(); + cudaMemcpy(ptr, property_ptr, sizeof(float) * num, cudaMemcpyDeviceToDevice); + } + break; + default: + LOG(FATAL) << "Invalid DataType"; + } + } + + private: + string property_name_; + DataType dtype_; + int depth_; + int channel_; +}; + +REGISTER_KERNEL_BUILDER(Name("OctreeProperty").Device(DEVICE_GPU), OctreePropertyOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/octree_samples.cc b/tensorflow/libs/octree_samples.cc new file mode 100644 index 0000000..25d9c89 --- /dev/null +++ b/tensorflow/libs/octree_samples.cc @@ -0,0 +1,47 @@ +#include "octree_samples.h" + +#include +#include +#include + + +namespace tensorflow { + +REGISTER_OP("OctreeSamples") + .Input("names: string") + .Output("octrees: string") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->input(0)); + return Status::OK(); + }) + .Doc(R"doc(Get one sample octree for testing.)doc"); + + +class OctreeSamplesOp : public OpKernel { + public: + explicit OctreeSamplesOp(OpKernelConstruction* context) : + OpKernel(context) {} + + void Compute(OpKernelContext* context) override { + const Tensor& names = context->input(0); + int num = names.NumElements(); + CHECK_GE(num, 1); + + Tensor* octrees = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, names.shape(), &octrees)); + + for (int i = 0; i < num; ++i) { + string name = names.flat()(i); + string& oct = octrees->flat()(i); + + size_t size = 0; + const char* str = (const char*)octree::get_one_octree(name.c_str(), &size); + oct.assign(str, str + size); + } + } +}; + + +REGISTER_KERNEL_BUILDER(Name("OctreeSamples").Device(DEVICE_CPU), OctreeSamplesOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/octree_search_op.cc b/tensorflow/libs/octree_search_op.cc new file mode 100644 index 0000000..5c51215 --- /dev/null +++ b/tensorflow/libs/octree_search_op.cc @@ -0,0 +1,95 @@ +#include "octree_nn.h" +#include "octree_parser.h" + +#include +#include +#include + +namespace tensorflow { + +REGISTER_OP("OctreeSearch") + .Input("xyz: float") // C * H + .Input("octree: int8") + .Attr("depth: int") + //.Attr("dtype: {int32,float32,uint32}") + .Output("kidx: int32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->MakeShape({ c->UnknownDim() })); + return Status::OK(); + }) + .Doc(R"doc(Octree search operator.)doc"); + + +class OctreeSearchOp : public OpKernel { + public: + explicit OctreeSearchOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("depth", &depth_)); + //OP_REQUIRES_OK(context, context->GetAttr("dtype", &dtype_)); + } + + void Compute(OpKernelContext* context) override { + // input + const Tensor& src_data = context->input(0); + auto src_ptr = src_data.flat().data(); + int channel = src_data.dim_size(0); + int src_h = src_data.dim_size(1); + CHECK_EQ(channel, 4) << "The input channel must be 4: x, y, z, id"; + + // coord2xyz + Tensor src_xyz_tensor; + coord2xyz_gpu_op(context, &src_xyz_tensor, src_ptr, src_h, channel); + const uint32* src_xyz = src_xyz_tensor.flat().data(); + + // xyz2key + Tensor src_key_tensor; + xyz2key_gpu_op(context, &src_key_tensor, src_xyz, src_h, depth_); + const uint32* src_key = src_key_tensor.flat().data(); + + // octree + OctreeParser octree_; + octree_.set_gpu(context->input(1).flat().data()); + int des_h = octree_.info().node_num(depth_); + const uint32* des_key = octree_.key_gpu(depth_); + Tensor des_key_tensor; + if (octree_.info().is_key2xyz()) { + xyz2key_gpu_op(context, &des_key_tensor, des_key, des_h, depth_); + des_key = des_key_tensor.flat().data(); + } + + // output + Tensor* des_tensor = nullptr; + TensorShape des_shape({ src_h }); + OP_REQUIRES_OK(context, context->allocate_output(0, des_shape, &des_tensor)); + auto idx_ptr = des_tensor->flat().data(); + + // binary search + search_key_gpu(idx_ptr, des_key, des_h, src_key, src_h); + } + + protected: + // todo: isolated the following functions out of this file + void xyz2key_gpu_op(OpKernelContext* context, Tensor* key_tensor, + const uint32* xyz, const int num, const int depth) { + OP_REQUIRES_OK(context, + context->allocate_temp(DT_UINT32, TensorShape({ num }), key_tensor)); + auto ptr = key_tensor->flat().data(); + xyz2key_gpu(ptr, xyz, num, depth); + } + + void coord2xyz_gpu_op(OpKernelContext* context, Tensor* xyz_tensor, + const float* coord, const int num, const int channel) { + OP_REQUIRES_OK(context, + context->allocate_temp(DT_UINT32, TensorShape({ num }), xyz_tensor)); + auto xyz = xyz_tensor->flat().data(); + coord2xyz_gpu(xyz, coord, num, channel); + } + + private: + int depth_; + //DataType dtype_; +}; + + +REGISTER_KERNEL_BUILDER(Name("OctreeSearch").Device(DEVICE_GPU), OctreeSearchOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/octree_set_property_op.cc b/tensorflow/libs/octree_set_property_op.cc new file mode 100644 index 0000000..19aeeb5 --- /dev/null +++ b/tensorflow/libs/octree_set_property_op.cc @@ -0,0 +1,108 @@ +#include "octree_parser.h" +#include "octree_info.h" + +#include +#include +#include +#include + +namespace tensorflow { + +REGISTER_OP("OctreeSetProperty") + .Input("in_octree: int8") + .Input("in_property: dtype") + .Attr("property_name: string") + .Attr("depth: int") + .Attr("dtype: {int32,float32,uint32}") + .Output("out_octree: int8") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->input(0)); + return Status::OK(); + }) + .Doc(R"doc(Octree set property operator.)doc"); + +class OctreeSetPropertyOp : public OpKernel { + public: + explicit OctreeSetPropertyOp(OpKernelConstruction* context) : + OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("property_name", &property_name_)); + OP_REQUIRES_OK(context, context->GetAttr("depth", &depth_)); + OP_REQUIRES_OK(context, context->GetAttr("dtype", &dtype_)); + } + + void Compute(OpKernelContext* context) override { + const Tensor& in_octree = context->input(0); + const Tensor& in_property = context->input(1); + auto in_ptr = in_octree.flat().data(); + + Tensor* out_octree; + OP_REQUIRES_OK(context, context->allocate_output(0, in_octree.shape(), &out_octree)); + auto out_ptr = out_octree->flat().data(); + cudaMemcpy(out_ptr, in_ptr, in_octree.NumElements(), cudaMemcpyDeviceToDevice); + + OctreeParser oct_parser; + int length; + void* property_ptr = nullptr; + oct_parser.set_gpu(out_ptr); + length = oct_parser.info().node_num(depth_); + if (property_name_ == "key") { + property_ptr = oct_parser.mutable_key_gpu(depth_); + CHECK_EQ(dtype_, DataType::DT_UINT32); + } else if (property_name_ == "child") { + property_ptr = oct_parser.mutable_children_gpu(depth_); + length *= oct_parser.info().channel(OctreeInfo::kChild); + CHECK_EQ(dtype_, DataType::DT_INT32); + } else if (property_name_ == "neigh") { + property_ptr = oct_parser.mutable_neighbor_gpu(depth_); + length *= oct_parser.info().channel(OctreeInfo::kNeigh); + CHECK_EQ(dtype_, DataType::DT_INT32); + } else if (property_name_ == "feature") { + property_ptr = oct_parser.mutable_feature_gpu(depth_); + length *= oct_parser.info().channel(OctreeInfo::kFeature); + CHECK_EQ(dtype_, DataType::DT_FLOAT); + } else if (property_name_ == "label") { + property_ptr = oct_parser.mutable_label_gpu(depth_); + length *= oct_parser.info().channel(OctreeInfo::kLabel); + CHECK_EQ(dtype_, DataType::DT_FLOAT); + } else if (property_name_ == "split") { + property_ptr = oct_parser.mutable_split_gpu(depth_); + length *= oct_parser.info().channel(OctreeInfo::kSplit); + CHECK_EQ(dtype_, DataType::DT_FLOAT); + } else { + LOG(FATAL) << "Unsupported Octree Property"; + } + CHECK_EQ(length, in_property.NumElements()) << "Wrong Property Size"; + switch (dtype_) { + case DataType::DT_UINT32: { + auto in_property_ptr = in_property.flat().data(); + cudaMemcpy(property_ptr, in_property_ptr, sizeof(uint32) * length, + cudaMemcpyDeviceToDevice); + } + break; + case DataType::DT_INT32: { + auto in_property_ptr = in_property.flat().data(); + cudaMemcpy(property_ptr, in_property_ptr, sizeof(int) * length, + cudaMemcpyDeviceToDevice); + } + break; + case DataType::DT_FLOAT: { + auto in_property_ptr = in_property.flat().data(); + cudaMemcpy(property_ptr, in_property_ptr, sizeof(float) * length, + cudaMemcpyDeviceToDevice); + } + break; + default: + LOG(FATAL) << "Wrong DataType"; + } + } + + private: + string property_name_; + DataType dtype_; + int depth_; + +}; + +REGISTER_KERNEL_BUILDER(Name("OctreeSetProperty").Device(DEVICE_GPU), OctreeSetPropertyOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/octree_update_op.cc b/tensorflow/libs/octree_update_op.cc new file mode 100644 index 0000000..18382f5 --- /dev/null +++ b/tensorflow/libs/octree_update_op.cc @@ -0,0 +1,77 @@ +#include "octree_nn.h" +#include "octree_parser.h" + +#include +#include +#include +#include + +namespace tensorflow { + +REGISTER_OP("OctreeUpdate") + .Input("in_octree: int8") + .Input("in_label: int32") + .Attr("depth: int") + .Attr("mask: int") + .Output("out_octree: int8") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->input(0)); + return Status::OK(); + }) + .Doc(R"doc(Octree update operator.)doc"); + + +class OctreeUpdateOp : public OpKernel { + public: + explicit OctreeUpdateOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("depth", &curr_depth_)); + OP_REQUIRES_OK(context, context->GetAttr("mask", &mask_)); + } + + void Compute(OpKernelContext* context) override { + // in octree + const Tensor& in_octree = context->input(0); + auto in_ptr = in_octree.flat().data(); + + // in label + const Tensor& in_label = context->input(1); + auto in_label_ptr = in_label.flat().data(); + + // out octree + Tensor* out_tensor = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, in_octree.shape(), &out_tensor)); + auto out_ptr = out_tensor->flat().data(); + cudaMemcpy(out_ptr, in_ptr, in_octree.NumElements(), cudaMemcpyDeviceToDevice); + + // parse octree info + OctreeInfo oct_info; + cudaMemcpy(&oct_info, out_ptr, sizeof(OctreeInfo), cudaMemcpyDeviceToHost); + OctreeParser octree_; + octree_.set_gpu(out_ptr, &oct_info); + int node_num = octree_.info().node_num(curr_depth_); + CHECK_EQ(node_num, in_label.NumElements()); + + // update children + int split_num = 0; // non-empty node number + int* children = octree_.mutable_children_gpu(curr_depth_); + generate_label_gpu(children, split_num, in_label_ptr, node_num, mask_); + + // deal with degenatated case + if (split_num == 0) { + split_num = 1; + memset_gpu(1, 0, children); + LOG(INFO) << "Warning: split_num == 0 in octree update layer."; + } + + oct_info.set_nempty(curr_depth_, split_num); + cudaMemcpy(out_ptr, &oct_info, sizeof(OctreeInfo), cudaMemcpyHostToDevice); + } + + private: + int curr_depth_; + int mask_; +}; + +REGISTER_KERNEL_BUILDER(Name("OctreeUpdate").Device(DEVICE_GPU), OctreeUpdateOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/points2octree_op.cc b/tensorflow/libs/points2octree_op.cc new file mode 100644 index 0000000..d75f7e1 --- /dev/null +++ b/tensorflow/libs/points2octree_op.cc @@ -0,0 +1,99 @@ +#include "octree.h" + +#include +#include +#include + + +namespace tensorflow { + +REGISTER_OP("PointsToOctree") + .Input("in_points: string") + .Attr("depth: int=6") + .Attr("full_depth: int=2") + .Attr("node_dis: bool=False") + .Attr("node_feature: bool=False") + .Attr("split_label: bool=False") + .Attr("adaptive: bool=False") + .Attr("adp_depth: int=4") + .Attr("th_normal: float=0.1") + .Attr("th_distance: float=2.0") + .Attr("extrapolate: bool=False") + .Attr("save_pts: bool=False") + .Attr("key2xyz: bool=False") + .Output("out_octree: string") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->input(0)); + return Status::OK(); + }) + .Doc(R"doc(Points To Octree operator.)doc"); + + +class PointsToOctreeOp : public OpKernel { + public: + explicit PointsToOctreeOp(OpKernelConstruction* context) : + OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("depth", &depth_)); + OP_REQUIRES_OK(context, context->GetAttr("full_depth", &full_depth_)); + OP_REQUIRES_OK(context, context->GetAttr("node_dis", &node_dis_)); + OP_REQUIRES_OK(context, context->GetAttr("node_feature", &node_feature_)); + OP_REQUIRES_OK(context, context->GetAttr("split_label", &split_label_)); + OP_REQUIRES_OK(context, context->GetAttr("adaptive", &adaptive_)); + OP_REQUIRES_OK(context, context->GetAttr("adp_depth", &adp_depth_)); + OP_REQUIRES_OK(context, context->GetAttr("th_distance", &th_distance_)); + OP_REQUIRES_OK(context, context->GetAttr("th_normal", &th_normal_)); + OP_REQUIRES_OK(context, context->GetAttr("extrapolate", &extrapolate_)); + OP_REQUIRES_OK(context, context->GetAttr("save_pts", &save_pts_)); + OP_REQUIRES_OK(context, context->GetAttr("key2xyz", &key2xyz_)); + } + + void Compute(OpKernelContext* context) override { + const Tensor& data_in = context->input(0); + CHECK_EQ(data_in.NumElements(), 1); + + // init the points + Points point_cloud_; + point_cloud_.set(data_in.flat()(0).data()); + + // check the points + string msg; + bool succ = point_cloud_.info().check_format(msg); + CHECK(succ) << msg; + + // init the octree info + OctreeInfo octree_info_; + octree_info_.initialize(depth_, full_depth_, node_dis_, + node_feature_, split_label_, adaptive_, adp_depth_, + th_distance_, th_normal_, key2xyz_, extrapolate_, + save_pts_, point_cloud_); + + // build the octree + Octree octree_; + octree_.build(octree_info_, point_cloud_); + const vector& octree_buf = octree_.buffer(); + + // output + Tensor* out_data = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, data_in.shape(), &out_data)); + string& out_str = out_data->flat()(0); + out_str.assign(octree_buf.begin(), octree_buf.end()); + } + + private: + int depth_; + int full_depth_; + bool node_dis_; + bool node_feature_; + bool split_label_; + bool adaptive_; + int adp_depth_; + float th_distance_; + float th_normal_; + bool extrapolate_; + bool save_pts_; + bool key2xyz_; +}; + + +REGISTER_KERNEL_BUILDER(Name("PointsToOctree").Device(DEVICE_CPU), PointsToOctreeOp); +} // namespace tensorflow diff --git a/tensorflow/libs/points_property_op.cc b/tensorflow/libs/points_property_op.cc new file mode 100644 index 0000000..7956ae4 --- /dev/null +++ b/tensorflow/libs/points_property_op.cc @@ -0,0 +1,99 @@ +#include "points_parser.h" + +#include +#include +#include + +namespace tensorflow { + +REGISTER_OP("PointsProperty") + .Input("points: string") + .Attr("property_name: string") + .Attr("channel: int") + .Output("out_property: float") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + int channel; + TF_RETURN_IF_ERROR(c->GetAttr("channel", &channel)); + c->set_output(0, c->MakeShape({ c->UnknownDim(), channel })); + return Status::OK(); + }) + .Doc(R"doc(Points property operator.)doc"); + +class PointsPropertyOp : public OpKernel { + public: + explicit PointsPropertyOp(OpKernelConstruction* context) : + OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("property_name", &property_name_)); + OP_REQUIRES_OK(context, context->GetAttr("channel", &channel_)); + } + + void Compute(OpKernelContext* context) override { + // input points + const Tensor& data_in = context->input(0); + auto points_buffer = data_in.flat(); + int batch_size = data_in.NumElements(); + vector points_in(batch_size); + for (int i = 0; i < batch_size; ++i) { + points_in[i].set(points_buffer(i).data()); + } + + // pt number + int total_num = 0; + vector npts(batch_size); + for (int i = 0; i < batch_size; ++i) { + npts[i] = points_in[i].info().pt_num(); + total_num += npts[i]; + } + + // channel + int channel = 0; + if (property_name_ == "xyz") { + channel = 4; // x, y, z, id + } else if (property_name_ == "label") { + channel = 1; + } else { + LOG(FATAL) << "Unsupported Property: " << property_name_; + } + CHECK_EQ(channel_, channel) << "The specified channel_ is wrong."; + + // init output + Tensor* out_tensor; + TensorShape out_shape({ total_num, channel }); + OP_REQUIRES_OK(context, context->allocate_output(0, out_shape, &out_tensor)); + float* out_ptr = out_tensor->flat().data(); + + // copy output + if (property_name_ == "xyz") { + for (int i = 0; i < batch_size; ++i) { + const float *in_ptr = points_in[i].points(); + for (int j = 0; j < npts[i]; ++j) { + int jx3 = j * 3, jx4 = j * 4; + for (int c = 0; c < 3; ++c) { + out_ptr[jx4 + c] = in_ptr[jx3 + c]; + } + out_ptr[jx4 + 3] = static_cast(i); // id + } + out_ptr += npts[i] * channel; + } + } else if (property_name_ == "label") { + for (int i = 0; i < batch_size; ++i) { + const float *in_ptr = points_in[i].label(); + CHECK(in_ptr != nullptr) << "The points have on labels"; + for (int j = 0; j < npts[i]; ++j) { + out_ptr[j] = in_ptr[j]; + } + out_ptr += npts[i]; + } + } else { + LOG(FATAL) << "Unsupported Property: " << property_name_; + } + } + + private: + string property_name_; + int channel_; +}; + +REGISTER_KERNEL_BUILDER(Name("PointsProperty").Device(DEVICE_CPU), PointsPropertyOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/tensorflow_gpu_gemm.cu.cc b/tensorflow/libs/tensorflow_gpu_gemm.cu.cc new file mode 100644 index 0000000..b1d9d98 --- /dev/null +++ b/tensorflow/libs/tensorflow_gpu_gemm.cu.cc @@ -0,0 +1,46 @@ +#define EIGEN_USE_THREADS +#define EIGEN_USE_GPU + +#include "tensorflow_gpu_gemm.h" + +#include +#include +#include + +namespace tensorflow { + +namespace { +perftools::gputools::DeviceMemory AsDeviceMemory( + const float* cuda_memory) { + perftools::gputools::DeviceMemoryBase wrapped( + const_cast(cuda_memory)); + perftools::gputools::DeviceMemory typed(wrapped); + return typed; +} +} // namespace + +void GEMMEngineTF::gemm(const bool transa, const bool transb, + const int m, const int n, const int k, const float alpha, + const float* a, const float* b, const float beta, float* c) { + perftools::gputools::blas::Transpose trans[] = { + perftools::gputools::blas::Transpose::kNoTranspose, + perftools::gputools::blas::Transpose::kTranspose + }; + + auto* stream = context_->op_device_context()->stream(); + OP_REQUIRES(context_, stream, errors::Internal("No GPU stream available.")); + + auto a_ptr = AsDeviceMemory(a); + auto b_ptr = AsDeviceMemory(b); + auto c_ptr = AsDeviceMemory(c); + + bool blas_launch_status = + stream + ->ThenBlasGemm(trans[transb], trans[transa], n, m, k, alpha, b_ptr, + transb ? k : n, a_ptr, transa ? m : k, beta, &c_ptr, n) + .ok(); + + OP_REQUIRES(context_, blas_launch_status, errors::Aborted("CuBlasGemm failed!")); +} + +} // namespace tensorflow \ No newline at end of file diff --git a/tensorflow/libs/tensorflow_gpu_gemm.h b/tensorflow/libs/tensorflow_gpu_gemm.h new file mode 100644 index 0000000..a569be2 --- /dev/null +++ b/tensorflow/libs/tensorflow_gpu_gemm.h @@ -0,0 +1,24 @@ +#ifndef _TENSORFLOW_GPU_GEMM_H_ +#define _TENSORFLOW_GPU_GEMM_H_ + +#include "gemm_engine.h" + +namespace tensorflow { + +class OpKernelContext; + +class GEMMEngineTF : public octree::GEMMEngine { + public: + void set_context(OpKernelContext* ctx) { context_ = ctx; } + + virtual void gemm(const bool TransA, const bool TransB, + const int M, const int N, const int K, const float alpha, + const float* A, const float* B, const float beta, float* C) override; + + public: + OpKernelContext* context_; +}; + +} // namespace tensorflow + +#endif // _TENSORFLOW_GPU_GEMM_H_ diff --git a/tensorflow/libs/transform_octree_op.cc b/tensorflow/libs/transform_octree_op.cc new file mode 100644 index 0000000..cda5695 --- /dev/null +++ b/tensorflow/libs/transform_octree_op.cc @@ -0,0 +1,116 @@ +#include +#include +#include + +#include "transform_octree.h" + +namespace tensorflow { + +REGISTER_OP("OctreeDrop") + .Input("octree_in: string") + .Input("depth: int32") + .Input("ratio: float") + .Output("octree_out: string") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->MakeShape({c->UnknownDim()})); + return Status::OK(); + }) + .Doc(R"doc(Drop out octree nodes.)doc"); + +REGISTER_OP("OctreeScan") + .Input("octree_in: string") + .Input("axis: float") + .Attr("scale: float=1.0") + .Output("octree_out: string") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->MakeShape({c->UnknownDim()})); + return Status::OK(); + }) + .Doc(R"doc(Drop octree nodes via scanning.)doc"); + +REGISTER_OP("OctreeCast") + .Input("octree_in: int8") + .Output("octree_out: string") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->MakeShape({1})); + return Status::OK(); + }) + .Doc(R"doc(Cast the octree tensor from `int8` to `string`.)doc"); + +class OctreeDropOp : public OpKernel { + public: + explicit OctreeDropOp(OpKernelConstruction* context) : OpKernel(context) {} + + void Compute(OpKernelContext* context) override { + // input + const Tensor& data_in = context->input(0); + const string& octree_in = data_in.flat()(0); + int depth = context->input(1).flat()(0); + float ratio = context->input(2).flat()(0); + + vector octree_out; + octree_dropout(octree_out, octree_in, depth, ratio); + + // output + Tensor* data_out = nullptr; + OP_REQUIRES_OK(context, + context->allocate_output(0, data_in.shape(), &data_out)); + string& str_out = data_out->flat()(0); + str_out.assign(octree_out.begin(), octree_out.end()); + } +}; + +class OctreeScanOp : public OpKernel { + public: + explicit OctreeScanOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("scale", &scale_)); + } + + void Compute(OpKernelContext* context) override { + // input + OctreeParser octree_in; + const Tensor& data_in = context->input(0); + octree_in.set_cpu(data_in.flat()(0).data()); + + const Tensor& axis_in = context->input(1); + auto ptr_in = axis_in.flat().data(); + vector axis(ptr_in, ptr_in + axis_in.NumElements()); + + ScanOctree scan_octree(scale_); + vector octree_out; + scan_octree.scan(octree_out, octree_in, axis); + + // output + Tensor* data_out = nullptr; + OP_REQUIRES_OK(context, + context->allocate_output(0, data_in.shape(), &data_out)); + string& str_out = data_out->flat()(0); + str_out.assign(octree_out.begin(), octree_out.end()); + } + + protected: + float scale_; +}; + +class OctreeCastOp : public OpKernel { + public: + explicit OctreeCastOp(OpKernelConstruction* context) : OpKernel(context) {} + + void Compute(OpKernelContext* context) override { + // input + const Tensor& data_in = context->input(0); + const char* ptr_in = (const char*)data_in.flat().data(); + + // output + Tensor* data_out = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, {1}, &data_out)); + string& str_out = data_out->flat()(0); + str_out.assign(ptr_in, ptr_in + data_in.NumElements()); + } +}; + +REGISTER_KERNEL_BUILDER(Name("OctreeCast").Device(DEVICE_CPU), OctreeCastOp); +REGISTER_KERNEL_BUILDER(Name("OctreeScan").Device(DEVICE_CPU), OctreeScanOp); +REGISTER_KERNEL_BUILDER(Name("OctreeDrop").Device(DEVICE_CPU), OctreeDropOp); + +} // namespace tensorflow diff --git a/tensorflow/libs/transform_points_op.cc b/tensorflow/libs/transform_points_op.cc new file mode 100644 index 0000000..2226e1c --- /dev/null +++ b/tensorflow/libs/transform_points_op.cc @@ -0,0 +1,434 @@ +#include "points.h" +#include "transform_points.h" +#include "math_functions.h" +#include // QQ revise: add for rand() +#include // QQ revise: add for pow() + +#include +#include +#include +#include //QQ revise: add for Tensor Shape + +using std::vector; + +namespace tensorflow { + +REGISTER_OP("TransformPoints") + .Input("points: string") + .Input("angle: float") + .Input("scale: float") + .Input("jitter: float") + .Input("radius: float") + .Input("center: float") + .Input("ratio: float") + .Input("dim: int32") + .Input("stddev: float") + .Attr("axis: string='y'") // todo: delete this attribute + .Attr("depth: int=6") + .Attr("offset: float=0.55") + .Output("points_out: string") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->input(0)); + return Status::OK(); + }) + .Doc(R"doc(Transform points.)doc"); + +REGISTER_OP("BoundingSphere") + .Input("points: string") + .Output("radius: float") + .Output("center: float") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->MakeShape({ 1 })); + c->set_output(1, c->MakeShape({ 3 })); + return Status::OK(); + }) + .Doc(R"doc(Compute the bounding sphere of a point cloud.)doc"); + +REGISTER_OP("MakeGrids") + .Input("points: string") + .Attr("out_size: int") + .Attr("feature_num: int") + .Output("grids: float") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + int out_size; + int feature_num; + TF_RETURN_IF_ERROR(c->GetAttr("out_size", &out_size)); + TF_RETURN_IF_ERROR(c->GetAttr("feature_num", &feature_num)); + c->set_output(0, + c->MakeShape({feature_num*out_size*out_size*out_size,1})); + return Status::OK(); + }) + .Doc(R"doc(Compute the grids of a point cloud.)doc"); + +class TransformPointsOp : public OpKernel { + public: + explicit TransformPointsOp(OpKernelConstruction* context) + : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("axis", &axis_)); + OP_REQUIRES_OK(context, context->GetAttr("depth", &depth_)); + OP_REQUIRES_OK(context, context->GetAttr("offset", &offset_)); + } + + void Compute(OpKernelContext* context) override { + // input + auto extract_param = [](float * vec, const Tensor & ts) { + for (int i = 0; i < 3 && i < ts.NumElements(); ++i) { + vec[i] = ts.flat()(i); + } + }; + const Tensor& data_in = context->input(0); + // float rotate = context->input(1).flat()(0); + float angle[3] = { 0 }; + extract_param(angle, context->input(1)); + float scales[3] = { 1.0f, 1.0f, 1.0f }; + extract_param(scales, context->input(2)); + float jitter[3] = { 0 }; + extract_param(jitter, context->input(3)); + float radius = context->input(4).flat()(0); + float center[3] = { 0 }; + extract_param(center, context->input(5)); + float ratio = context->input(6).flat()(0); + int dim = context->input(7).flat()(0); + float stddev[3] = { 0 }; // std_points, std_normals, std_features + extract_param(stddev, context->input(8)); + +// std::cout << angle[0] << " " << scales[0] << " " << jitter[0] << " " << radius[0] << " " << center[0] << " " << stddev[0] << std::endl; + + // check + CHECK_EQ(data_in.NumElements(), 1); + for (int i = 0; i < 3; ++i) { + CHECK_GE(scales[i], 0.1f) << "The scale should be larger than 0.1"; + } + + // copy the data out of the input tensor + auto points_array = data_in.flat(); + vector points_buf(points_array(0).begin(), points_array(0).end()); + + // init the points + Points pts; + pts.set(points_buf.data()); + + // check the points + string msg; + bool succ = pts.info().check_format(msg); + CHECK(succ) << msg; + + int N = pts.info().pt_num(); +// std::cout << "N in transorm points: " << N << std::endl; + + // centralize & displacement // QQ revise: we do not want to add offset. and adjust the radius + const float kEPS = 1.0e-10f; + // float dis[3] = { -center[0], -center[1], -center[2] }; + // pts.translate(dis); + // if (offset_ > kEPS) { + // // !!! rescale the offset, relative to the octree node size + // float offset = offset_ * 2.0f * radius / float(1 << depth_); + // pts.displace(offset); + // radius += offset; + // } + + // data augmentation: rotate the point cloud + if (fabs(angle[0]) > kEPS || fabs(angle[1]) > kEPS || fabs(angle[2]) > kEPS) { + // float axes[] = { 0.0f, 0.0f, 0.0f }; + // if (axis_ == "x") axes[0] = 1.0f; + // else if (axis_ == "y") axes[1] = 1.0f; + // else axes[2] = 1.0f; + // pts.rotate(rotate, axes); + + if (axis_ == "x") { angle[1] = angle[2] = 0; pts.rotate(angle);} + else if (axis_ == "y") { angle[0] = angle[2] = 0; pts.rotate(angle);} + else if (axis_ == "z") { angle[0] = angle[1] = 0; pts.rotate(angle); } + else if (axis_ == "xyz") //QQ revise: randomly along xyz. + { + int rand_num = rand()%6; + + if (rand_num == 0){ + float x_angle[3] = { 0 }; + x_angle[0] = angle[0]; + pts.rotate(x_angle); + + float y_angle[3] = { 0 }; + y_angle[1] = angle[1]; + pts.rotate(y_angle); + + float z_angle[3] = { 0 }; + z_angle[2] = angle[2]; + pts.rotate(z_angle); + } + + if (rand_num == 1){ + float x_angle[3] = { 0 }; + x_angle[0] = angle[0]; + pts.rotate(x_angle); + + float z_angle[3] = { 0 }; + z_angle[2] = angle[2]; + pts.rotate(z_angle); + + float y_angle[3] = { 0 }; + y_angle[1] = angle[1]; + pts.rotate(y_angle); + } + + if (rand_num == 2){ + float y_angle[3] = { 0 }; + y_angle[1] = angle[1]; + pts.rotate(y_angle); + + float x_angle[3] = { 0 }; + x_angle[0] = angle[0]; + pts.rotate(x_angle); + + float z_angle[3] = { 0 }; + z_angle[2] = angle[2]; + pts.rotate(z_angle); + } + + if (rand_num == 3){ + float y_angle[3] = { 0 }; + y_angle[1] = angle[1]; + pts.rotate(y_angle); + + float z_angle[3] = { 0 }; + z_angle[2] = angle[2]; + pts.rotate(z_angle); + + float x_angle[3] = { 0 }; + x_angle[0] = angle[0]; + pts.rotate(x_angle); + } + + if (rand_num == 4){ + + float z_angle[3] = { 0 }; + z_angle[2] = angle[2]; + pts.rotate(z_angle); + + float x_angle[3] = { 0 }; + x_angle[0] = angle[0]; + pts.rotate(x_angle); + + float y_angle[3] = { 0 }; + y_angle[1] = angle[1]; + pts.rotate(y_angle); + } + + if (rand_num == 5){ + float z_angle[3] = { 0 }; + z_angle[2] = angle[2]; + pts.rotate(z_angle); + + float y_angle[3] = { 0 }; + y_angle[1] = angle[1]; + pts.rotate(y_angle); + + float x_angle[3] = { 0 }; + x_angle[0] = angle[0]; + pts.rotate(x_angle); + } + + } + else {} +// pts.rotate(angle); + } + + // jitter // QQ revise: we can keep the jitter, in our case, + float max_jitter = -1.0; + for (int i = 0; i < 3; i++) { + // !!! rescale the jitter, relative to the radius + jitter[i] *= 2.0 * radius; + if (max_jitter < fabs(jitter[i])) { + max_jitter = fabs(jitter[i]); + } + } + if (fabs(max_jitter) > kEPS) { + pts.translate(jitter); + //radius += max_jitter; + } + + // scale to [-1, 1]^3 + if (radius == 0) radius = kEPS; + float max_scale = -1.0f; + for (int i = 0; i < 3; ++i) { + scales[i] /= radius; + if (max_scale < scales[i]) { max_scale = scales[i]; } + } + pts.scale(scales); + + // add noise + if (stddev[0] > 0 || stddev[1] > 0 || stddev[2] > 0) { + pts.add_noise(stddev[0], stddev[1]); + } + + // clip the points to the box[-1, 1] ^ 3, + const float bbmin[] = { -1.0f, -1.0f, -1.0f }; + const float bbmax[] = { 1.0f, 1.0f, 1.0f }; + if (max_scale > 1.0f || max_jitter > kEPS) { + pts.clip(bbmin, bbmax); + } + + // dropout points // qq: omit +// if (dim > 0 && ratio > 0) { +// DropPoints drop_points(dim, ratio, bbmin, bbmax); +// drop_points.dropout(pts); +// } + + // output + Tensor* out_data = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, data_in.shape(), &out_data)); + string& out_str = out_data->flat()(0); + out_str.assign(points_buf.begin(), points_buf.end()); + } + + + private: + string axis_; + int depth_; + float offset_; +}; + +class BoundingSphereOp : public OpKernel { + public: + explicit BoundingSphereOp(OpKernelConstruction* context) + : OpKernel(context) {} + + void Compute(OpKernelContext* context) override { + const Tensor& data_in = context->input(0); + CHECK_EQ(data_in.NumElements(), 1); + + // init the points + Points pts; + pts.set(data_in.flat()(0).data()); + + // check the points + string msg; + bool succ = pts.info().check_format(msg); + CHECK(succ) << msg; + + // bounding sphere + float radius, center[3]; + bounding_sphere(radius, center, pts.points(), pts.info().pt_num()); + + // output + Tensor* out0 = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, TensorShape({ 1 }), &out0)); + float* ptr0 = out0->flat().data(); + ptr0[0] = radius; + + Tensor* out1 = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(1, TensorShape({ 3 }), &out1)); + float* ptr1 = out1->flat().data(); + for (int i = 0; i < 3; ++i) { ptr1[i] = center[i]; } + } +}; + +class MakeGridsOp : public OpKernel { + public: + explicit MakeGridsOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("out_size", &out_size_)); + } + + void Compute(OpKernelContext* context) override { + // Grab the input tensor + const Tensor& data_in = context->input(0); + // copy the data out of the input tensor + auto points_array = data_in.flat(); + vector points_buf(points_array(0).begin(), points_array(0).end()); + +// std::cout << "out size: " << out_size_ << std::endl; + // init the points + Points pts; + pts.set(points_buf.data()); + + // check the points + string msg; + bool succ = pts.info().check_format(msg); + CHECK(succ) << msg; + +// std::cout << "Test 0" << std::endl; + int N = pts.info().pt_num(); +// std::cout << "N in Make Grid: " << N << std::endl; +// int size = pow(2, depth_); + int channels = pts.info().channel(PointsInfo::kFeature) + 3; + + float* coords = pts.mutable_points(); + float* normals = pts.mutable_normal(); + float* features = pts.mutable_feature(); + +// std::cout << "Test 1" << std::endl; + + // Create an output tensor + Tensor* output_tensor = NULL; + OP_REQUIRES_OK(context, context->allocate_output(0, TensorShape({channels*out_size_*out_size_*out_size_,1}), + &output_tensor)); + auto output_flat = output_tensor->flat(); + + vector count; + vector feature_agg; + count.clear(); + feature_agg.clear(); + count.resize(out_size_*out_size_*out_size_*channels); + feature_agg.resize(out_size_*out_size_*out_size_*channels); +// std::cout << "Test 2" << std::endl; + + int x,y,z; + int idx; + for (int i = 0; i < N; i++) { + x = (coords[3*i] + 1) *out_size_/2 ; + y = (coords[3*i+1] + 1) *out_size_/2; + z = (coords[3*i+2] + 1) *out_size_/2; + + for (int j = 0; j < channels; j++){ + idx = j * pow(out_size_, 3) + x * pow(out_size_, 2) + y * out_size_ + z; +// if (i<10){std::cout << "x,y,z: " << x << " " << y << " " << z << std::endl;} + if (j<3) {feature_agg[idx] += normals[3*i + j];} + else {feature_agg[idx] += features[3*i + j - 3];} + count[idx] += 1; + } + + } + + int div; +// int non_zero_count = 0; +// int feature_non_zero; + for (int x = 0; x < out_size_; x++){ + for (int y = 0; y < out_size_; y++){ + for (int z = 0; z < out_size_; z++){ + +// feature_non_zero = 0; + for (int j = 0; j < channels; j++){ + idx = j * pow(out_size_, 3) + x * pow(out_size_, 2) + y * out_size_ + z; + div = count[idx]; + if (div != 0) { + output_flat(idx) = feature_agg[idx]/div; +// std::cout << "x,y,z: " << x << " " << y << " " << z << " " << idx << std::endl; + } + else{ + output_flat(idx) = 0; +// if (feature_agg[idx] !=0){std::cout << "check feature agg" <