├── 3d_matching_eval ├── src │ ├── calculations │ │ ├── __init__.py │ │ ├── cost_matrix.py │ │ ├── iou.py │ │ └── rigid_registration.py │ ├── .env │ ├── utils │ │ ├── __init__.py │ │ ├── serializers.py │ │ ├── logger.py │ │ ├── profiling.py │ │ └── view.py │ ├── logging.yaml │ ├── config.py │ ├── main.py │ ├── fake_gt_gen.py │ ├── loader.py │ └── matching.py ├── requirements.txt └── README.md ├── 2d_floorplan_eval ├── IOU_precision_recall │ ├── __pycache__ │ │ ├── main.cpython-36.pyc │ │ ├── Topo_FP.cpython-36.pyc │ │ ├── FileIO_FP.cpython-36.pyc │ │ ├── Viewer_FP.cpython-36.pyc │ │ ├── Utility_FP.cpython-36.pyc │ │ └── Conversion_DWG_FP.cpython-36.pyc │ ├── Conversion_DWG_FP.py │ ├── ipynb │ │ ├── Conversion_DWG_FP.ipynb │ │ ├── Utility_FP.ipynb │ │ ├── FileIO_FP.ipynb │ │ ├── main.ipynb │ │ ├── Topo_FP.ipynb │ │ └── Viewer_FP.ipynb │ ├── Utility_FP.py │ ├── FileIO_FP.py │ ├── main.py │ ├── Topo_FP.py │ └── Viewer_FP.py ├── warping_error │ ├── warping_error.i │ ├── warping_error.h │ ├── main.cpp │ ├── utility.h │ ├── warping_error.cpp │ └── utility.cpp ├── 3P │ └── jsonpp │ │ └── nlohmann │ │ ├── detail │ │ ├── meta │ │ │ ├── void_t.hpp │ │ │ ├── detected.hpp │ │ │ ├── cpp_future.hpp │ │ │ └── is_sax.hpp │ │ ├── macro_unscope.hpp │ │ ├── input │ │ │ └── position_t.hpp │ │ ├── iterators │ │ │ ├── internal_iterator.hpp │ │ │ ├── iterator_traits.hpp │ │ │ ├── primitive_iterator.hpp │ │ │ ├── json_reverse_iterator.hpp │ │ │ └── iteration_proxy.hpp │ │ ├── json_ref.hpp │ │ ├── value_t.hpp │ │ ├── output │ │ │ └── output_adapters.hpp │ │ └── macro_scope.hpp │ │ ├── adl_serializer.hpp │ │ ├── json_fwd.hpp │ │ └── thirdparty │ │ └── hedley │ │ └── hedley_undef.hpp ├── Makefile └── README.md ├── .gitignore └── README.md /3d_matching_eval/src/calculations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /3d_matching_eval/src/.env: -------------------------------------------------------------------------------- 1 | # https://numba.pydata.org/numba-doc/dev/reference/envvars.html#envvar-NUMBA_DEBUG 2 | NUMBA_DEBUG=0 3 | -------------------------------------------------------------------------------- /2d_floorplan_eval/IOU_precision_recall/__pycache__/main.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GradientSpaces/cv4aec-challenge/HEAD/2d_floorplan_eval/IOU_precision_recall/__pycache__/main.cpython-36.pyc -------------------------------------------------------------------------------- /2d_floorplan_eval/IOU_precision_recall/__pycache__/Topo_FP.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GradientSpaces/cv4aec-challenge/HEAD/2d_floorplan_eval/IOU_precision_recall/__pycache__/Topo_FP.cpython-36.pyc -------------------------------------------------------------------------------- /2d_floorplan_eval/IOU_precision_recall/__pycache__/FileIO_FP.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GradientSpaces/cv4aec-challenge/HEAD/2d_floorplan_eval/IOU_precision_recall/__pycache__/FileIO_FP.cpython-36.pyc -------------------------------------------------------------------------------- /2d_floorplan_eval/IOU_precision_recall/__pycache__/Viewer_FP.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GradientSpaces/cv4aec-challenge/HEAD/2d_floorplan_eval/IOU_precision_recall/__pycache__/Viewer_FP.cpython-36.pyc -------------------------------------------------------------------------------- /2d_floorplan_eval/IOU_precision_recall/__pycache__/Utility_FP.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GradientSpaces/cv4aec-challenge/HEAD/2d_floorplan_eval/IOU_precision_recall/__pycache__/Utility_FP.cpython-36.pyc -------------------------------------------------------------------------------- /2d_floorplan_eval/IOU_precision_recall/__pycache__/Conversion_DWG_FP.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GradientSpaces/cv4aec-challenge/HEAD/2d_floorplan_eval/IOU_precision_recall/__pycache__/Conversion_DWG_FP.cpython-36.pyc -------------------------------------------------------------------------------- /3d_matching_eval/requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib>=3.5.1 2 | numba>=0.55.1 3 | numpy>=1.21.5 4 | opencv-python-headless>=4.5.5.64 5 | pandas>=1.3.5 6 | python-dotenv>=0.20.0 7 | scipy>=1.7.3 8 | typer>=0.4.1 9 | PyYaml>=6.0 10 | pycpd>=2.0.0 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | .vscode 3 | .DS_store 4 | 5 | # Python 6 | __pycache__ 7 | 8 | # Data & Build 9 | 2d_floorplan_eval/build/ 10 | 2d_floorplan_eval/samples/ 11 | 2d_floorplan_eval/Makefile.vc 12 | 13 | 3d_matching_eval/data 14 | 3d_matching_eval/output 15 | 3d_matching_eval/reference_test* 16 | -------------------------------------------------------------------------------- /3d_matching_eval/src/utils/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | "load_logger", 3 | "profile", 4 | "NumpyArrayEncoder", 5 | "plot" 6 | ] 7 | 8 | from .logger import load_logger 9 | from .profiling import profile 10 | from .serializers import NumpyArrayEncoder 11 | from .view import plot 12 | -------------------------------------------------------------------------------- /2d_floorplan_eval/warping_error/warping_error.i: -------------------------------------------------------------------------------- 1 | %module warping_error 2 | 3 | %include "std_string.i" 4 | %include "std_vector.i" 5 | 6 | using namespace std; 7 | 8 | %template(cpp_nested1_IntVector) vector; 9 | 10 | %{ 11 | #include "warping_error.h" 12 | %} 13 | 14 | //double-check that this is indeed %include !!! 15 | %include "warping_error.h" -------------------------------------------------------------------------------- /2d_floorplan_eval/3P/jsonpp/nlohmann/detail/meta/void_t.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nlohmann 4 | { 5 | namespace detail 6 | { 7 | template struct make_void 8 | { 9 | using type = void; 10 | }; 11 | template using void_t = typename make_void::type; 12 | } // namespace detail 13 | } // namespace nlohmann 14 | -------------------------------------------------------------------------------- /3d_matching_eval/src/logging.yaml: -------------------------------------------------------------------------------- 1 | version: 1 2 | formatters: 3 | simple: 4 | format: '%(asctime)s - %(levelname)s - %(message)s' 5 | handlers: 6 | console: 7 | class: logging.StreamHandler 8 | level: DEBUG 9 | formatter: simple 10 | stream: ext://sys.stdout 11 | loggers: 12 | sampleLogger: 13 | level: DEBUG 14 | handlers: [console] 15 | propagate: no 16 | root: 17 | level: DEBUG 18 | handlers: [console] 19 | -------------------------------------------------------------------------------- /3d_matching_eval/src/utils/serializers.py: -------------------------------------------------------------------------------- 1 | import json 2 | import numpy as np 3 | 4 | class NumpyArrayEncoder(json.JSONEncoder): 5 | def default(self, obj): 6 | if isinstance(obj, np.ndarray): 7 | return obj.tolist() 8 | if isinstance(obj, np.floating): 9 | return float(obj) 10 | if isinstance(obj, np.integer): 11 | return int(obj) 12 | return json.JSONEncoder.default(self, obj) 13 | 14 | -------------------------------------------------------------------------------- /2d_floorplan_eval/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Allvision IO, Inc. 2 | # author: ryan@allvision.io 3 | all: build/linux/warping_error 4 | 5 | build/linux/warping_error: warping_error/warping_error.cpp warping_error/main.cpp warping_error/warping_error.h 6 | mkdir -p build/linux 7 | g++ -o build/linux/warping_error -I 3P/jsonpp warping_error/warping_error.cpp warping_error/main.cpp warping_error/utility.cpp -l opencv_core -l opencv_imgproc -l opencv_imgcodecs -l opencv_highgui 8 | -------------------------------------------------------------------------------- /3d_matching_eval/src/utils/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import logging.config 3 | import yaml 4 | 5 | 6 | def load_logger() -> None: 7 | """Open default logging configuration and use it as default config for root logger.""" 8 | try: 9 | with open('logging.yaml', 'r') as f: 10 | config = yaml.safe_load(f.read()) 11 | logging.config.dictConfig(config) 12 | except Exception: 13 | logging.basicConfig(level=logging.INFO) -------------------------------------------------------------------------------- /2d_floorplan_eval/3P/jsonpp/nlohmann/detail/macro_unscope.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // restore GCC/clang diagnostic settings 4 | #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) 5 | #pragma GCC diagnostic pop 6 | #endif 7 | #if defined(__clang__) 8 | #pragma GCC diagnostic pop 9 | #endif 10 | 11 | // clean up 12 | #undef JSON_INTERNAL_CATCH 13 | #undef JSON_CATCH 14 | #undef JSON_THROW 15 | #undef JSON_TRY 16 | #undef JSON_HAS_CPP_14 17 | #undef JSON_HAS_CPP_17 18 | #undef NLOHMANN_BASIC_JSON_TPL_DECLARATION 19 | #undef NLOHMANN_BASIC_JSON_TPL 20 | 21 | #include 22 | -------------------------------------------------------------------------------- /2d_floorplan_eval/3P/jsonpp/nlohmann/detail/input/position_t.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // size_t 4 | 5 | namespace nlohmann 6 | { 7 | namespace detail 8 | { 9 | /// struct to capture the start position of the current token 10 | struct position_t 11 | { 12 | /// the total number of characters read 13 | std::size_t chars_read_total = 0; 14 | /// the number of characters read in the current line 15 | std::size_t chars_read_current_line = 0; 16 | /// the number of lines read 17 | std::size_t lines_read = 0; 18 | 19 | /// conversion to size_t to preserve SAX interface 20 | constexpr operator size_t() const 21 | { 22 | return chars_read_total; 23 | } 24 | }; 25 | 26 | } // namespace detail 27 | } // namespace nlohmann 28 | -------------------------------------------------------------------------------- /3d_matching_eval/src/config.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | """ Enable Numba & CUDA optimized algorithms. 4 | All of the algorithms are defined in `calculations.py`.""" 5 | enable_optimization: bool = True 6 | 7 | """ Normalize user's predictions with ground-truth no matter the initial vertices parameters are. 8 | Normalization includes rotation, scale, translation, ratios approximation. 9 | In the result data outputted all the differences will be specified.""" 10 | enable_normalization: bool = False 11 | 12 | """ Metrics (precision, recall, f1) and 3D IoU thresholds specified in ground-truth units. 13 | The maximum is also used as LAP threshold value where the L2 norm is the default measurement.""" 14 | metrics_thresholds: List[float] = [0.05, 0.10, 0.20] 15 | iou_thresholds: List[float] = [0.25, 0.50, 0.75] 16 | 17 | """ Debug.""" 18 | debug: bool = False 19 | -------------------------------------------------------------------------------- /2d_floorplan_eval/3P/jsonpp/nlohmann/detail/iterators/internal_iterator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace nlohmann 6 | { 7 | namespace detail 8 | { 9 | /*! 10 | @brief an iterator value 11 | 12 | @note This structure could easily be a union, but MSVC currently does not allow 13 | unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. 14 | */ 15 | template struct internal_iterator 16 | { 17 | /// iterator for JSON objects 18 | typename BasicJsonType::object_t::iterator object_iterator {}; 19 | /// iterator for JSON arrays 20 | typename BasicJsonType::array_t::iterator array_iterator {}; 21 | /// generic iterator for all other types 22 | primitive_iterator_t primitive_iterator {}; 23 | }; 24 | } // namespace detail 25 | } // namespace nlohmann 26 | -------------------------------------------------------------------------------- /3d_matching_eval/src/calculations/cost_matrix.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numba as nb 3 | 4 | from numba import njit, prange 5 | from config import enable_optimization 6 | 7 | @njit 8 | def calculate_cost_matrix_numba( 9 | ground: nb.float32[:, :], 10 | target: nb.float32[:, :] 11 | ) -> nb.float32[:, :]: 12 | # Performance tested: 15x faster ~ 1s. 13 | costs = np.ones((ground.shape[0], target.shape[0]), np.float32) 14 | for i in prange(ground.shape[0]): 15 | for j in prange(target.shape[0]): 16 | costs[i, j] = np.sqrt(np.power(ground[i] - target[j], 2))[0] 17 | return costs 18 | 19 | def calculate_cost_matrix_python( 20 | ground: np.ndarray, 21 | target: np.ndarray, 22 | ) -> np.ndarray: 23 | costs = np.full((ground.shape[0], target.shape[0]), np.nan, dtype=np.float32) 24 | for i in range(ground.shape[0]): 25 | for j in range(target.shape[0]): 26 | costs[i, j] = np.sqrt(np.power(ground[i] - target[j], 2))[0] 27 | return costs 28 | 29 | if enable_optimization: 30 | calculate_cost_matrix = calculate_cost_matrix_numba 31 | else: 32 | calculate_cost_matrix = calculate_cost_matrix_python 33 | -------------------------------------------------------------------------------- /2d_floorplan_eval/warping_error/warping_error.h: -------------------------------------------------------------------------------- 1 | #ifndef WARPING_ERROR 2 | #define WARPING_ERROR 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class Warping_error { 11 | public: 12 | Warping_error() {} 13 | ~Warping_error() {} 14 | 15 | bool decide_simple_points(const cv::Mat& patch); 16 | 17 | cv::Mat python2mat_vector_uchar( 18 | const int height, 19 | const int width, 20 | std::vector& data 21 | ); 22 | 23 | cv::Mat extract_patch( 24 | const cv::Mat& image, 25 | const std::pair& p, 26 | const int kernel_size 27 | ); 28 | 29 | void update_simple_points( 30 | const cv::Mat& image, 31 | const std::pair& p, 32 | const int kernel_size, 33 | std::set>& simple_points 34 | ); 35 | 36 | float compute_warping_error( 37 | const cv::Mat& LSTAR, 38 | const cv::Mat& T, 39 | bool display 40 | ); 41 | 42 | int compute_warping_error_python( 43 | const int height, 44 | const int width, 45 | std::vector& data_o, 46 | std::vector& data_d, 47 | std::vector& result 48 | ); 49 | }; 50 | 51 | #endif // !WARPING_ERROR -------------------------------------------------------------------------------- /2d_floorplan_eval/warping_error/main.cpp: -------------------------------------------------------------------------------- 1 | #include "warping_error.h" 2 | #include "utility.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main(int argc, char* argv[]) { 9 | 10 | Utility ut; 11 | std::string path_jsn1 = argv[1]; 12 | std::string path_jsn2 = argv[2]; 13 | std::vector>> geometry1 = ut.read_geometry_JSON(path_jsn1, "20cm"); 14 | std::vector>> geometry2 = ut.read_geometry_JSON(path_jsn2, "20cm"); 15 | std::vector>> geometry_wrt1 = ut.cvt_geometry_format_obj2drw(geometry1); 16 | std::vector>> geometry_wrt2 = ut.cvt_geometry_format_obj2drw(geometry2); 17 | std::vector> x1_1, y1_1, x2_1, y2_1; 18 | std::vector> x1_2, y1_2, x2_2, y2_2; 19 | ut.cvt_geometry2list(geometry_wrt1, x1_1, y1_1, x2_1, y2_1); 20 | ut.cvt_geometry2list(geometry_wrt2, x1_2, y1_2, x2_2, y2_2); 21 | int height, width; 22 | ut.determine_curtain_size_sync(x1_1, y1_1, x2_1, y2_1, x1_2, y1_2, x2_2, y2_2, height, width); 23 | cv::Mat img1 = ut.plot_layers(x1_1, y1_1, x2_1, y2_1, height, width, 1, -1); 24 | cv::Mat img2 = ut.plot_layers(x1_2, y1_2, x2_2, y2_2, height, width, 1, -1); 25 | cv::flip(img1, img1, 0); 26 | cv::flip(img2, img2, 0); 27 | 28 | Warping_error we; 29 | std::cout << we.compute_warping_error(img1, img2, false) << std::endl; 30 | 31 | system("pause"); 32 | return 0; 33 | } -------------------------------------------------------------------------------- /2d_floorplan_eval/3P/jsonpp/nlohmann/detail/iterators/iterator_traits.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // random_access_iterator_tag 4 | 5 | #include 6 | #include 7 | 8 | namespace nlohmann 9 | { 10 | namespace detail 11 | { 12 | template 13 | struct iterator_types {}; 14 | 15 | template 16 | struct iterator_types < 17 | It, 18 | void_t> 20 | { 21 | using difference_type = typename It::difference_type; 22 | using value_type = typename It::value_type; 23 | using pointer = typename It::pointer; 24 | using reference = typename It::reference; 25 | using iterator_category = typename It::iterator_category; 26 | }; 27 | 28 | // This is required as some compilers implement std::iterator_traits in a way that 29 | // doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. 30 | template 31 | struct iterator_traits 32 | { 33 | }; 34 | 35 | template 36 | struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> 37 | : iterator_types 38 | { 39 | }; 40 | 41 | template 42 | struct iterator_traits::value>> 43 | { 44 | using iterator_category = std::random_access_iterator_tag; 45 | using value_type = T; 46 | using difference_type = ptrdiff_t; 47 | using pointer = T*; 48 | using reference = T&; 49 | }; 50 | } // namespace detail 51 | } // namespace nlohmann 52 | -------------------------------------------------------------------------------- /2d_floorplan_eval/3P/jsonpp/nlohmann/adl_serializer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace nlohmann 9 | { 10 | 11 | template 12 | struct adl_serializer 13 | { 14 | /*! 15 | @brief convert a JSON value to any value type 16 | 17 | This function is usually called by the `get()` function of the 18 | @ref basic_json class (either explicit or via conversion operators). 19 | 20 | @param[in] j JSON value to read from 21 | @param[in,out] val value to write to 22 | */ 23 | template 24 | static auto from_json(BasicJsonType&& j, ValueType& val) noexcept( 25 | noexcept(::nlohmann::from_json(std::forward(j), val))) 26 | -> decltype(::nlohmann::from_json(std::forward(j), val), void()) 27 | { 28 | ::nlohmann::from_json(std::forward(j), val); 29 | } 30 | 31 | /*! 32 | @brief convert any value type to a JSON value 33 | 34 | This function is usually called by the constructors of the @ref basic_json 35 | class. 36 | 37 | @param[in,out] j JSON value to write to 38 | @param[in] val value to read from 39 | */ 40 | template 41 | static auto to_json(BasicJsonType& j, ValueType&& val) noexcept( 42 | noexcept(::nlohmann::to_json(j, std::forward(val)))) 43 | -> decltype(::nlohmann::to_json(j, std::forward(val)), void()) 44 | { 45 | ::nlohmann::to_json(j, std::forward(val)); 46 | } 47 | }; 48 | 49 | } // namespace nlohmann 50 | -------------------------------------------------------------------------------- /2d_floorplan_eval/README.md: -------------------------------------------------------------------------------- 1 | ## OBJ File Format 2 | The floorplan is available in both a binary obj format and a plain text JSON format. For the OBJ format, the header contains following information: 3 | 4 | ``` 5 | number of layers (unsigned int) 6 | number of structures for each layer (unsigned int array) 7 | 8 |  1. length of layer name (unsigned int) 9 |  2. layer name (bytes array) 10 |  3. information for layer 1/structure 1: 11 |   a. number of points for structure (unsigned int) 12 |   b. pt1.X, pt1.Y, pt2.X, pt2.Y, ... (float in meters) 13 |  4. information for layer 1/structure 2: 14 |  ... 15 | : 16 | ... 17 | ``` 18 | 19 | The detailed file format is provided here: 20 | ``` 21 | [number of layers][number of structures for each layer] 22 | [length of layer 1's name][layer 1 name] 23 | [number of points for structure 1][pt1.X][pt1.Y][pt2.X][pt2.Y]... 24 | [number of points for structure 2][pt1.X][pt1.Y][pt2.X][pt2.Y]... 25 | ... 26 | [length of layer 2's name][layer 2 name] 27 | [number of points for structure 1][pt1.X][pt1.Y][pt2.X][pt2.Y]... 28 | [number of points for structure 2][pt1.X][pt1.Y][pt2.X][pt2.Y]... 29 | ... 30 | ``` 31 | 32 | ## Sample OBJ file 33 | A sample obj file is given below (it should be in binary practically): 34 | ``` 35 | 2 32 15 36 | 6 "A_WALL" 37 | 2 320.12 442.55 320.12 445.55 38 | 2 322.12 447.55 322.12 449.55 39 | ... 40 | 6 "A_DOOR" 41 | 4 178.12 336.55 225.12 482.55 389.12 557.55 175.12 882.55 42 | 2 389.12 557.55 175.12 882.55 43 | ``` 44 | ## JSON File Format 45 | ``` 46 | 1. number of layers 47 | 2. number of structures in each layer 48 | 3. layer details 49 |  - layer name 50 |  - number of points 51 |  - x y coordinates for each point 52 | ``` 53 | -------------------------------------------------------------------------------- /2d_floorplan_eval/3P/jsonpp/nlohmann/detail/meta/detected.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | // http://en.cppreference.com/w/cpp/experimental/is_detected 8 | namespace nlohmann 9 | { 10 | namespace detail 11 | { 12 | struct nonesuch 13 | { 14 | nonesuch() = delete; 15 | ~nonesuch() = delete; 16 | nonesuch(nonesuch const&) = delete; 17 | nonesuch(nonesuch const&&) = delete; 18 | void operator=(nonesuch const&) = delete; 19 | void operator=(nonesuch&&) = delete; 20 | }; 21 | 22 | template class Op, 25 | class... Args> 26 | struct detector 27 | { 28 | using value_t = std::false_type; 29 | using type = Default; 30 | }; 31 | 32 | template class Op, class... Args> 33 | struct detector>, Op, Args...> 34 | { 35 | using value_t = std::true_type; 36 | using type = Op; 37 | }; 38 | 39 | template