├── CMakeLists.txt ├── README-en.md ├── README.md ├── common.hpp ├── dbnet.cpp ├── include └── dirent.h ├── logging.h └── utils.h /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | 3 | project(dbnet) 4 | 5 | add_definitions(-std=c++11) 6 | 7 | option(CUDA_USE_STATIC_CUDA_RUNTIME OFF) 8 | set(CMAKE_CXX_STANDARD 11) 9 | set(CMAKE_BUILD_TYPE Debug) 10 | 11 | #设置cuda信息 12 | find_package(CUDA REQUIRED) 13 | 14 | message(STATUS " libraries: ${CUDA_LIBRARIES}") 15 | message(STATUS " include path: ${CUDA_INCLUDE_DIRS}") 16 | include_directories(${CUDA_INCLUDE_DIRS}) 17 | set(CUDA_NVCC_PLAGS ${CUDA_NVCC_PLAGS};-std=c++11; -g; -G;-gencode; arch=compute_75;code=sm_75) 18 | enable_language(CUDA) # 这一句添加后 ,就会在vs中不需要再手动设置cuda 19 | 20 | include_directories(${PROJECT_SOURCE_DIR}/include) 21 | 22 | include_directories(D:\\TensorRT-7.0.0.11.Windows10.x86_64.cuda-10.2.cudnn7.6\\TensorRT-7.0.0.11\\include) 23 | 24 | #-D_MWAITXINTRIN_H_INCLUDED 解决error: identifier "__builtin_ia32_mwaitx" is undefined 25 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Ofast -D_MWAITXINTRIN_H_INCLUDED") 26 | 27 | # 设置opencv的信息 28 | set(OpenCV_DIR "D:\\opencv\\opencv346\\build") 29 | find_package(OpenCV QUIET 30 | NO_MODULE 31 | NO_DEFAULT_PATH 32 | NO_CMAKE_PATH 33 | NO_CMAKE_ENVIRONMENT_PATH 34 | NO_SYSTEM_ENVIRONMENT_PATH 35 | NO_CMAKE_PACKAGE_REGISTRY 36 | NO_CMAKE_BUILDS_PATH 37 | NO_CMAKE_SYSTEM_PATH 38 | NO_CMAKE_SYSTEM_PACKAGE_REGISTRY 39 | ) 40 | 41 | message(STATUS "OpenCV library status:") 42 | message(STATUS " version: ${OpenCV_VERSION}") 43 | message(STATUS " libraries: ${OpenCV_LIBS}") 44 | message(STATUS " include path: ${OpenCV_INCLUDE_DIRS}") 45 | 46 | include_directories(${OpenCV_INCLUDE_DIRS}) 47 | 48 | link_directories(D:\\TensorRT-7.0.0.11.Windows10.x86_64.cuda-10.2.cudnn7.6\\TensorRT-7.0.0.11\\lib) 49 | 50 | add_executable(dbnet ${PROJECT_SOURCE_DIR}/dbnet.cpp) 51 | 52 | target_link_libraries(dbnet "nvinfer" "nvinfer_plugin" "nvparsers" "nvonnxparser") 53 | #target_link_libraries(dbnet "cudart") 54 | target_link_libraries(dbnet ${OpenCV_LIBS}) 55 | target_link_libraries(dbnet ${CUDA_LIBRARIES}) 56 | 57 | add_definitions(-pthread) 58 | 59 | -------------------------------------------------------------------------------- /README-en.md: -------------------------------------------------------------------------------- 1 | # DBNet 2 | 3 | The Pytorch implementation is [DBNet](https://github.com/BaofengZan/DBNet.pytorch). 4 | 5 | ## How to Run 6 | 7 | The CmakeLists.txt provided by this repository is the win version. Please see the last link for Linux version.。 8 | 9 | * 1 Generate wts 10 | Download the code and model from the pytoch repository. After configuring the environment 11 | 12 | 13 | In tools/predict.py, set the save_wts attribute to True. After running, the wts file will be generated in the tools folder. 14 | 15 | You can also export onnx. Set the onnx property to True. 16 | 17 | * 2 cmake generates a VS project, then compile and run. Different parameters passed in the main function have different functions. -s is to generate engin files. -d imgpath stands for running forward. 18 | 19 | ![image-20200807183412846](https://user-images.githubusercontent.com/20653176/89722330-00c36900-da1b-11ea-97f4-c61f9cd196fa.png) 20 | 21 | ## linux version 22 | 23 | https://github.com/wang-xinyu/tensorrtx 24 | 25 | 26 | ## Insufficient 27 | 28 | * ~~1 In the common file, the following two functions can be combined.~~ 29 | 30 | ```c++ 31 | ILayer* convBnLeaky(INetworkDefinition *network, std::map& weightMap, ITensor& input, int outch, int ksize, int s, int g, std::string lname, bool bias = true) 32 | ``` 33 | 34 | ```c++ 35 | ILayer* convBnLeaky2(INetworkDefinition *network, std::map& weightMap, ITensor& input, int outch, int ksize, int s, int g, std::string lname, bool bias = true) 36 | ``` 37 | 38 | * 2 There are many differences between the post-processing and the pytorch version, which can be improved. 39 | * 3 ~~Data preprocessing in pyorch is to resize the short side of the image to 1024, scale the long side proportionally, and finally cut the new width and height to a multiple of 32. Resize the image directly to 640*640 in your own repo, which is more crude.~~ 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DBNet 2 | 3 | The Pytorch implementation is [DBNet](https://github.com/BaofengZan/DBNet.pytorch). 4 | 5 | ## How to Run 6 | 7 | 本仓库提供的CmakeLists.txt为win版本。Linux版本请看最后的链接。 8 | 9 | * 1 生成wts 10 | 从pytoch仓库下载到代码和模型。配置好环境后 11 | 12 | 在tools/predict.py中,将save_wts属性置为True,运行后,就在在tools文件夹下生成wts文件。 13 | 14 | 同时也可以导出onnx。将onnx属性设置为True即可。 15 | 16 | * 2 cmake 生成VS工程,然后编译,运行。main函数中传入不同的参数,有不同的作用。 -s为生成engin文件。 -d imgpath 代表运行前向。 17 | 18 |

19 | 20 |

21 | 22 | ## linux版本 23 | 24 | https://github.com/wang-xinyu/tensorrtx 25 | 26 | 27 | 28 | ## 不足 29 | 30 | * ~~1 common文件中,下面两个函数可以合并,自己偷了个懒。~~ 31 | 32 | ```c++ 33 | ILayer* convBnLeaky(INetworkDefinition *network, std::map& weightMap, ITensor& input, int outch, int ksize, int s, int g, std::string lname, bool bias = true) 34 | ``` 35 | 36 | ```c++ 37 | ILayer* convBnLeaky2(INetworkDefinition *network, std::map& weightMap, ITensor& input, int outch, int ksize, int s, int g, std::string lname, bool bias = true) 38 | ``` 39 | 40 | * 2 后处理中与pytorch版本也有好多不同之处,这都是可以改进提升的。 41 | * 3 ~~在pyorch中数据预处理是将图像短边resize到1024,长边按比例缩放,最后将新的宽高截到32的倍数。而在自己的repo中直接将图像resize到640*640,较为粗暴。~~ 42 | -------------------------------------------------------------------------------- /common.hpp: -------------------------------------------------------------------------------- 1 | #ifndef YOLOV5_COMMON_H_ 2 | #define YOLOV5_COMMON_H_ 3 | 4 | #include s 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "dirent.h" 11 | #include "NvInfer.h" 12 | #include 13 | 14 | #define CHECK(status) \ 15 | do\ 16 | {\ 17 | auto ret = (status);\ 18 | if (ret != 0)\ 19 | {\ 20 | std::cerr << "Cuda failure: " << ret << std::endl;\ 21 | abort();\ 22 | }\ 23 | } while (0) 24 | 25 | using namespace nvinfer1; 26 | 27 | // TensorRT weight files have a simple space delimited format: 28 | // [type] [size] 29 | std::map loadWeights(const std::string file) { 30 | std::cout << "Loading weights: " << file << std::endl; 31 | std::map weightMap; 32 | 33 | // Open weights file 34 | std::ifstream input(file); 35 | assert(input.is_open() && "Unable to load weight file."); 36 | 37 | // Read number of weight blobs 38 | int32_t count; 39 | input >> count; 40 | assert(count > 0 && "Invalid weight map file."); 41 | 42 | while (count--) 43 | { 44 | Weights wt{DataType::kFLOAT, nullptr, 0}; 45 | uint32_t size; 46 | 47 | // Read name and type of blob 48 | std::string name; 49 | input >> name >> std::dec >> size; 50 | wt.type = DataType::kFLOAT; 51 | 52 | // Load blob 53 | uint32_t* val = reinterpret_cast(malloc(sizeof(val) * size)); 54 | for (uint32_t x = 0, y = size; x < y; ++x) 55 | { 56 | input >> std::hex >> val[x]; 57 | } 58 | wt.values = val; 59 | 60 | wt.count = size; 61 | weightMap[name] = wt; 62 | } 63 | 64 | return weightMap; 65 | } 66 | 67 | IScaleLayer* addBatchNorm2d(INetworkDefinition *network, std::map& weightMap, ITensor& input, std::string lname, float eps) { 68 | float *gamma = (float*)weightMap[lname + ".weight"].values; 69 | float *beta = (float*)weightMap[lname + ".bias"].values; 70 | float *mean = (float*)weightMap[lname + ".running_mean"].values; 71 | float *var = (float*)weightMap[lname + ".running_var"].values; 72 | int len = weightMap[lname + ".running_var"].count; 73 | 74 | float *scval = reinterpret_cast(malloc(sizeof(float) * len)); 75 | for (int i = 0; i < len; i++) { 76 | scval[i] = gamma[i] / sqrt(var[i] + eps); 77 | } 78 | Weights scale{DataType::kFLOAT, scval, len}; 79 | 80 | float *shval = reinterpret_cast(malloc(sizeof(float) * len)); 81 | for (int i = 0; i < len; i++) { 82 | shval[i] = beta[i] - mean[i] * gamma[i] / sqrt(var[i] + eps); 83 | } 84 | Weights shift{DataType::kFLOAT, shval, len}; 85 | 86 | float *pval = reinterpret_cast(malloc(sizeof(float) * len)); 87 | for (int i = 0; i < len; i++) { 88 | pval[i] = 1.0; 89 | } 90 | Weights power{DataType::kFLOAT, pval, len}; 91 | 92 | weightMap[lname + ".scale"] = scale; 93 | weightMap[lname + ".shift"] = shift; 94 | weightMap[lname + ".power"] = power; 95 | IScaleLayer* scale_1 = network->addScale(input, ScaleMode::kCHANNEL, shift, scale, power); 96 | assert(scale_1); 97 | return scale_1; 98 | } 99 | 100 | ILayer* convBnLeaky(INetworkDefinition *network, std::map& weightMap, ITensor& input, int outch, int ksize, int s, int g, std::string lname, std::string bnname, bool bias = true) { 101 | Weights emptywts{DataType::kFLOAT, nullptr, 0}; 102 | int p = ksize / 2; 103 | IConvolutionLayer* conv1 = nullptr; 104 | if (bias) 105 | { 106 | conv1 = network->addConvolutionNd(input, outch, DimsHW{ ksize, ksize }, weightMap[lname + ".weight"], weightMap[lname + ".bias"]); 107 | } 108 | else 109 | { 110 | conv1 = network->addConvolutionNd(input, outch, DimsHW{ ksize, ksize }, weightMap[lname + ".weight"], emptywts); 111 | } 112 | assert(conv1); 113 | conv1->setStrideNd(DimsHW{s, s}); 114 | conv1->setPaddingNd(DimsHW{p, p}); 115 | conv1->setNbGroups(g); 116 | //IScaleLayer* bn1 = addBatchNorm2d(network, weightMap, *conv1->getOutput(0), lname + ".bn", 1e-4); 117 | IScaleLayer* bn1 = addBatchNorm2d(network, weightMap, *conv1->getOutput(0), lname.substr(0, lname.find_last_of(".")) + bnname, 1e-5); 118 | auto lr = network->addActivation(*bn1->getOutput(0), ActivationType::kRELU); 119 | lr->setAlpha(0.1); 120 | return lr; 121 | } 122 | 123 | 124 | IActivationLayer* basicBlock(INetworkDefinition *network, std::map& weightMap, ITensor& input, int inch, int outch, int stride, std::string lname) { 125 | Weights emptywts{ DataType::kFLOAT, nullptr, 0 }; 126 | 127 | IConvolutionLayer* conv1 = network->addConvolution(input, outch, DimsHW{ 3, 3 }, weightMap[lname + "conv1.weight"], emptywts); 128 | assert(conv1); 129 | conv1->setStride(DimsHW{ stride, stride }); 130 | conv1->setPadding(DimsHW{ 1, 1 }); 131 | 132 | IScaleLayer* bn1 = addBatchNorm2d(network, weightMap, *conv1->getOutput(0), lname + "bn1", 1e-5); 133 | 134 | IActivationLayer* relu1 = network->addActivation(*bn1->getOutput(0), ActivationType::kRELU); 135 | assert(relu1); 136 | 137 | IConvolutionLayer* conv2 = network->addConvolution(*relu1->getOutput(0), outch, DimsHW{ 3, 3 }, weightMap[lname + "conv2.weight"], emptywts); 138 | assert(conv2); 139 | conv2->setPadding(DimsHW{ 1, 1 }); 140 | 141 | IScaleLayer* bn2 = addBatchNorm2d(network, weightMap, *conv2->getOutput(0), lname + "bn2", 1e-5); 142 | 143 | IElementWiseLayer* ew1; 144 | if (inch != outch) { 145 | IConvolutionLayer* conv3 = network->addConvolution(input, outch, DimsHW{ 1, 1 }, weightMap[lname + "downsample.0.weight"], emptywts); 146 | assert(conv3); 147 | conv3->setStride(DimsHW{ stride, stride }); 148 | IScaleLayer* bn3 = addBatchNorm2d(network, weightMap, *conv3->getOutput(0), lname + "downsample.1", 1e-5); 149 | ew1 = network->addElementWise(*bn3->getOutput(0), *bn2->getOutput(0), ElementWiseOperation::kSUM); 150 | } 151 | else { 152 | ew1 = network->addElementWise(input, *bn2->getOutput(0), ElementWiseOperation::kSUM); 153 | } 154 | IActivationLayer* relu2 = network->addActivation(*ew1->getOutput(0), ActivationType::kRELU); 155 | assert(relu2); 156 | return relu2; 157 | } 158 | 159 | int read_files_in_dir(const char *p_dir_name, std::vector &file_names) { 160 | DIR *p_dir = opendir(p_dir_name); 161 | if (p_dir == nullptr) { 162 | return -1; 163 | } 164 | 165 | struct dirent* p_file = nullptr; 166 | while ((p_file = readdir(p_dir)) != nullptr) { 167 | if (strcmp(p_file->d_name, ".") != 0 && 168 | strcmp(p_file->d_name, "..") != 0) { 169 | //std::string cur_file_name(p_dir_name); 170 | //cur_file_name += "/"; 171 | //cur_file_name += p_file->d_name; 172 | std::string cur_file_name(p_file->d_name); 173 | file_names.push_back(cur_file_name); 174 | } 175 | } 176 | closedir(p_dir); 177 | return 0; 178 | } 179 | 180 | #endif 181 | 182 | -------------------------------------------------------------------------------- /dbnet.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "cuda_runtime_api.h" 4 | #include "logging.h" 5 | #include "common.hpp" 6 | #include 7 | 8 | #define USE_FP16 // comment out this if want to use FP32 9 | #define DEVICE 0 // GPU id 10 | #define EXPANDRATIO 1.4 11 | static const int SHORT_INPUT = 640; 12 | static const int MAX_INPUT_SIZE = 1440; // 32x 13 | static const int MIN_INPUT_SIZE = 608; 14 | static const int OPT_INPUT_W = 1152; 15 | static const int OPT_INPUT_H = 640; 16 | const char* INPUT_BLOB_NAME = "data"; 17 | const char* OUTPUT_BLOB_NAME = "out"; 18 | static Logger gLogger; 19 | 20 | cv::RotatedRect expandBox(const cv::RotatedRect& inBox, float ratio = 1.0) { 21 | cv::Size size = inBox.size; 22 | int neww = size.width * ratio; 23 | int newh = size.height *ratio; 24 | return cv::RotatedRect(inBox.center, cv::Size(neww, newh), inBox.angle); 25 | } 26 | float paddimg(cv::Mat& In_Out_img, int shortsize = 960) { 27 | int w = In_Out_img.cols; 28 | int h = In_Out_img.rows; 29 | float scale = 1.f; 30 | if (w < h) { 31 | scale = (float)shortsize / w; 32 | h = scale * h; 33 | w = shortsize; 34 | } 35 | else { 36 | scale = (float)shortsize / h; 37 | w = scale * w; 38 | h = shortsize; 39 | } 40 | 41 | if (h % 32 != 0) { 42 | h = (h / 32 + 1) * 32; 43 | } 44 | if (w % 32 != 0) { 45 | w = (w / 32 + 1) * 32; 46 | } 47 | 48 | cv::resize(In_Out_img, In_Out_img, cv::Size(w, h)); 49 | return scale; 50 | } 51 | // Creat the engine using only the API and not any parser. 52 | ICudaEngine* createEngine(unsigned int maxBatchSize, IBuilder* builder, IBuilderConfig* config, DataType dt) { 53 | const auto explicitBatch = 1U << static_cast(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH); 54 | INetworkDefinition* network = builder->createNetworkV2(explicitBatch); 55 | // Create input tensor of shape {3, INPUT_H, INPUT_W} with name INPUT_BLOB_NAME 56 | ITensor* data = network->addInput(INPUT_BLOB_NAME, dt, Dims4{ 1, 3, -1, -1 }); 57 | assert(data); 58 | 59 | std::map weightMap = loadWeights("E:\\LearningCodes\\DBNET\\DBNet.pytorch\\tools\\DBNet.wts"); 60 | Weights emptywts{ DataType::kFLOAT, nullptr, 0 }; 61 | 62 | /* ------ Resnet18 backbone------ */ 63 | // Add convolution layer with 6 outputs and a 5x5 filter. 64 | IConvolutionLayer* conv1 = network->addConvolution(*data, 64, DimsHW{ 7, 7 }, weightMap["backbone.conv1.weight"], emptywts); 65 | assert(conv1); 66 | conv1->setStride(DimsHW{ 2, 2 }); 67 | conv1->setPadding(DimsHW{ 3, 3 }); 68 | 69 | IScaleLayer* bn1 = addBatchNorm2d(network, weightMap, *conv1->getOutput(0), "backbone.bn1", 1e-5); 70 | IActivationLayer* relu1 = network->addActivation(*bn1->getOutput(0), ActivationType::kRELU); 71 | assert(relu1); 72 | IPoolingLayer* pool1 = network->addPooling(*relu1->getOutput(0), PoolingType::kMAX, DimsHW{ 3, 3 }); 73 | assert(pool1); 74 | pool1->setStride(DimsHW{ 2, 2 }); 75 | pool1->setPadding(DimsHW{ 1, 1 }); 76 | 77 | IActivationLayer* relu2 = basicBlock(network, weightMap, *pool1->getOutput(0), 64, 64, 1, "backbone.layer1.0."); 78 | IActivationLayer* relu3 = basicBlock(network, weightMap, *relu2->getOutput(0), 64, 64, 1, "backbone.layer1.1."); // x2 79 | 80 | IActivationLayer* relu4 = basicBlock(network, weightMap, *relu3->getOutput(0), 64, 128, 2, "backbone.layer2.0."); 81 | IActivationLayer* relu5 = basicBlock(network, weightMap, *relu4->getOutput(0), 128, 128, 1, "backbone.layer2.1."); // x3 82 | 83 | IActivationLayer* relu6 = basicBlock(network, weightMap, *relu5->getOutput(0), 128, 256, 2, "backbone.layer3.0."); 84 | IActivationLayer* relu7 = basicBlock(network, weightMap, *relu6->getOutput(0), 256, 256, 1, "backbone.layer3.1."); //x4 85 | 86 | IActivationLayer* relu8 = basicBlock(network, weightMap, *relu7->getOutput(0), 256, 512, 2, "backbone.layer4.0."); 87 | IActivationLayer* relu9 = basicBlock(network, weightMap, *relu8->getOutput(0), 512, 512, 1, "backbone.layer4.1."); //x5 88 | 89 | /* ------- FPN neck ------- */ 90 | ILayer* p5 = convBnLeaky(network, weightMap, *relu9->getOutput(0), 64, 1, 1, 1, "neck.reduce_conv_c5.conv", ".bn"); // k=1 s = 1 p = k/2=1/2=0 91 | ILayer* c4_1 = convBnLeaky(network, weightMap, *relu7->getOutput(0), 64, 1, 1, 1, "neck.reduce_conv_c4.conv", ".bn"); 92 | 93 | float *deval = reinterpret_cast(malloc(sizeof(float) * 64 * 2 * 2)); 94 | for (int i = 0; i < 64 * 2 * 2; i++) { 95 | deval[i] = 1.0; 96 | } 97 | Weights deconvwts1{ DataType::kFLOAT, deval, 64 * 2 * 2 }; 98 | IDeconvolutionLayer* p4_1 = network->addDeconvolutionNd(*p5->getOutput(0), 64, DimsHW{ 2, 2 }, deconvwts1, emptywts); 99 | p4_1->setStrideNd(DimsHW{ 2, 2 }); 100 | p4_1->setNbGroups(64); 101 | weightMap["deconv1"] = deconvwts1; 102 | 103 | IElementWiseLayer* p4_add = network->addElementWise(*p4_1->getOutput(0), *c4_1->getOutput(0), ElementWiseOperation::kSUM); 104 | ILayer* p4 = convBnLeaky(network, weightMap, *p4_add->getOutput(0), 64, 3, 1, 1, "neck.smooth_p4.conv", ".bn"); // smooth 105 | ILayer* c3_1 = convBnLeaky(network, weightMap, *relu5->getOutput(0), 64, 1, 1, 1, "neck.reduce_conv_c3.conv", ".bn"); 106 | 107 | Weights deconvwts2{ DataType::kFLOAT, deval, 64 * 2 * 2 }; 108 | IDeconvolutionLayer* p3_1 = network->addDeconvolutionNd(*p4->getOutput(0), 64, DimsHW{ 2, 2 }, deconvwts2, emptywts); 109 | p3_1->setStrideNd(DimsHW{ 2, 2 }); 110 | p3_1->setNbGroups(64); 111 | 112 | IElementWiseLayer* p3_add = network->addElementWise(*p3_1->getOutput(0), *c3_1->getOutput(0), ElementWiseOperation::kSUM); 113 | ILayer* p3 = convBnLeaky(network, weightMap, *p3_add->getOutput(0), 64, 3, 1, 1, "neck.smooth_p3.conv", ".bn"); // smooth 114 | ILayer* c2_1 = convBnLeaky(network, weightMap, *relu3->getOutput(0), 64, 1, 1, 1, "neck.reduce_conv_c2.conv", ".bn"); 115 | 116 | Weights deconvwts3{ DataType::kFLOAT, deval, 64 * 2 * 2 }; 117 | IDeconvolutionLayer* p2_1 = network->addDeconvolutionNd(*p3->getOutput(0), 64, DimsHW{ 2, 2 }, deconvwts3, emptywts); 118 | p2_1->setStrideNd(DimsHW{ 2, 2 }); 119 | p2_1->setNbGroups(64); 120 | 121 | IElementWiseLayer* p2_add = network->addElementWise(*p2_1->getOutput(0), *c2_1->getOutput(0), ElementWiseOperation::kSUM); 122 | ILayer* p2 = convBnLeaky(network, weightMap, *p2_add->getOutput(0), 64, 3, 1, 1, "neck.smooth_p2.conv", ".bn"); // smooth 123 | 124 | Weights deconvwts4{ DataType::kFLOAT, deval, 64 * 2 * 2 }; 125 | IDeconvolutionLayer* p3_up_p2 = network->addDeconvolutionNd(*p3->getOutput(0), 64, DimsHW{ 2, 2 }, deconvwts4, emptywts); 126 | p3_up_p2->setStrideNd(DimsHW{ 2, 2 }); 127 | p3_up_p2->setNbGroups(64); 128 | 129 | float *deval2 = reinterpret_cast(malloc(sizeof(float) * 64 * 8 * 8)); 130 | for (int i = 0; i < 64 * 8 * 8; i++) { 131 | deval2[i] = 1.0; 132 | } 133 | Weights deconvwts5{ DataType::kFLOAT, deval2, 64 * 8 * 8 }; 134 | IDeconvolutionLayer* p4_up_p2 = network->addDeconvolutionNd(*p4->getOutput(0), 64, DimsHW{ 8, 8 }, deconvwts5, emptywts); 135 | p4_up_p2->setPadding(DimsHW{ 2, 2 }); 136 | p4_up_p2->setStrideNd(DimsHW{ 4, 4 }); 137 | p4_up_p2->setNbGroups(64); 138 | weightMap["deconv2"] = deconvwts5; 139 | 140 | Weights deconvwts6{ DataType::kFLOAT, deval2, 64 * 8 * 8 }; 141 | IDeconvolutionLayer* p5_up_p2 = network->addDeconvolutionNd(*p5->getOutput(0), 64, DimsHW{ 8, 8 }, deconvwts6, emptywts); 142 | p5_up_p2->setStrideNd(DimsHW{ 8, 8 }); 143 | p5_up_p2->setNbGroups(64); 144 | 145 | // torch.cat([p2, p3, p4, p5], dim=1) 146 | ITensor* inputTensors[] = { p2->getOutput(0), p3_up_p2->getOutput(0), p4_up_p2->getOutput(0), p5_up_p2->getOutput(0) }; 147 | IConcatenationLayer* neck_cat = network->addConcatenation(inputTensors, 4); 148 | 149 | ILayer* neck_out = convBnLeaky(network, weightMap, *neck_cat->getOutput(0), 256, 3, 1, 1, "neck.conv.0", ".1"); // smooth 150 | assert(neck_out); 151 | ILayer* binarize1 = convBnLeaky(network, weightMap, *neck_out->getOutput(0), 64, 3, 1, 1, "head.binarize.0", ".1"); // 152 | Weights deconvwts7{ DataType::kFLOAT, deval, 64 * 2 * 2 }; 153 | IDeconvolutionLayer* binarizeup = network->addDeconvolutionNd(*binarize1->getOutput(0), 64, DimsHW{ 2, 2 }, deconvwts7, emptywts); 154 | binarizeup->setStrideNd(DimsHW{ 2, 2 }); 155 | binarizeup->setNbGroups(64); 156 | IScaleLayer* binarizebn1 = addBatchNorm2d(network, weightMap, *binarizeup->getOutput(0), "head.binarize.4", 1e-5); 157 | IActivationLayer* binarizerelu1 = network->addActivation(*binarizebn1->getOutput(0), ActivationType::kRELU); 158 | assert(binarizerelu1); 159 | 160 | Weights deconvwts8{ DataType::kFLOAT, deval, 64 * 2 * 2 }; 161 | IDeconvolutionLayer* binarizeup2 = network->addDeconvolutionNd(*binarizerelu1->getOutput(0), 64, DimsHW{ 2, 2 }, deconvwts8, emptywts); 162 | binarizeup2->setStrideNd(DimsHW{ 2, 2 }); 163 | binarizeup2->setNbGroups(64); 164 | 165 | IConvolutionLayer* binarize3 = network->addConvolution(*binarizeup2->getOutput(0), 1, DimsHW{ 3, 3 }, weightMap["head.binarize.7.weight"], weightMap["head.binarize.7.bias"]); 166 | assert(binarize3); 167 | binarize3->setStride(DimsHW{ 1, 1 }); 168 | binarize3->setPadding(DimsHW{ 1, 1 }); 169 | IActivationLayer* binarize4 = network->addActivation(*binarize3->getOutput(0), ActivationType::kSIGMOID); 170 | assert(binarize4); 171 | 172 | //threshold_maps = self.thresh(x) 173 | ILayer* thresh1 = convBnLeaky(network, weightMap, *neck_out->getOutput(0), 64, 3, 1, 1, "head.thresh.0", ".1", false); // 174 | Weights deconvwts9{ DataType::kFLOAT, deval, 64 * 2 * 2 }; 175 | IDeconvolutionLayer* threshup = network->addDeconvolutionNd(*thresh1->getOutput(0), 64, DimsHW{ 2, 2 }, deconvwts9, emptywts); 176 | threshup->setStrideNd(DimsHW{ 2, 2 }); 177 | threshup->setNbGroups(64); 178 | IConvolutionLayer* thresh2 = network->addConvolution(*threshup->getOutput(0), 64, DimsHW{ 3, 3 }, weightMap["head.thresh.3.1.weight"], weightMap["head.thresh.3.1.bias"]); 179 | assert(thresh2); 180 | thresh2->setStride(DimsHW{ 1, 1 }); 181 | thresh2->setPadding(DimsHW{ 1, 1 }); 182 | 183 | IScaleLayer* threshbn1 = addBatchNorm2d(network, weightMap, *thresh2->getOutput(0), "head.thresh.4", 1e-5); 184 | IActivationLayer* threshrelu1 = network->addActivation(*threshbn1->getOutput(0), ActivationType::kRELU); 185 | assert(threshrelu1); 186 | 187 | Weights deconvwts10{ DataType::kFLOAT, deval, 64 * 2 * 2 }; 188 | IDeconvolutionLayer* threshup2 = network->addDeconvolutionNd(*threshrelu1->getOutput(0), 64, DimsHW{ 2, 2 }, deconvwts10, emptywts); 189 | threshup2->setStrideNd(DimsHW{ 2, 2 }); 190 | threshup2->setNbGroups(64); 191 | IConvolutionLayer* thresh3 = network->addConvolution(*threshup2->getOutput(0), 1, DimsHW{ 3, 3 }, weightMap["head.thresh.6.1.weight"], weightMap["head.thresh.6.1.bias"]); 192 | assert(thresh3); 193 | thresh3->setStride(DimsHW{ 1, 1 }); 194 | thresh3->setPadding(DimsHW{ 1, 1 }); 195 | IActivationLayer* thresh4 = network->addActivation(*thresh3->getOutput(0), ActivationType::kSIGMOID); 196 | assert(thresh4); 197 | 198 | ITensor* inputTensors2[] = { binarize4->getOutput(0), thresh4->getOutput(0) }; 199 | IConcatenationLayer* head_out = network->addConcatenation(inputTensors2, 2); 200 | 201 | // y = F.interpolate(y, size=(H, W)) 202 | head_out->getOutput(0)->setName(OUTPUT_BLOB_NAME); 203 | network->markOutput(*head_out->getOutput(0)); 204 | 205 | IOptimizationProfile* profile = builder->createOptimizationProfile(); 206 | profile->setDimensions(INPUT_BLOB_NAME, OptProfileSelector::kMIN, Dims4(1, 3, MIN_INPUT_SIZE, MIN_INPUT_SIZE)); 207 | profile->setDimensions(INPUT_BLOB_NAME, OptProfileSelector::kOPT, Dims4(1, 3, OPT_INPUT_H, OPT_INPUT_W)); 208 | profile->setDimensions(INPUT_BLOB_NAME, OptProfileSelector::kMAX, Dims4(1, 3, MAX_INPUT_SIZE, MAX_INPUT_SIZE)); 209 | config->addOptimizationProfile(profile); 210 | 211 | // Build engine 212 | builder->setMaxBatchSize(maxBatchSize); 213 | config->setMaxWorkspaceSize(16 * (1 << 20)); // 16MB 214 | #ifdef USE_FP16 215 | config->setFlag(BuilderFlag::kFP16); 216 | #endif 217 | std::cout << "Building engine, please wait for a while..." << std::endl; 218 | ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config); 219 | std::cout << "Build engine successfully!" << std::endl; 220 | 221 | // Don't need the network any more 222 | network->destroy(); 223 | 224 | // Release host memory 225 | for (auto& mem : weightMap) { 226 | free((void*)(mem.second.values)); 227 | } 228 | 229 | return engine; 230 | } 231 | 232 | void APIToModel(unsigned int maxBatchSize, IHostMemory** modelStream) { 233 | // Create builder 234 | IBuilder* builder = createInferBuilder(gLogger); 235 | IBuilderConfig* config = builder->createBuilderConfig(); 236 | 237 | // Create model to populate the network, then set the outputs and create an engine 238 | ICudaEngine* engine = createEngine(maxBatchSize, builder, config, DataType::kFLOAT); 239 | //ICudaEngine* engine = createEngine(maxBatchSize, builder, config, DataType::kFLOAT); 240 | assert(engine != nullptr); 241 | 242 | // Serialize the engine 243 | (*modelStream) = engine->serialize(); 244 | 245 | // Close everything down 246 | engine->destroy(); 247 | builder->destroy(); 248 | } 249 | 250 | void doInference(IExecutionContext& context, float* input, float* output, int h_scale, int w_scale) { 251 | const ICudaEngine& engine = context.getEngine(); 252 | 253 | // Pointers to input and output device buffers to pass to engine. 254 | // Engine requires exactly IEngine::getNbBindings() number of buffers. 255 | assert(engine.getNbBindings() == 2); 256 | void* buffers[2]; 257 | 258 | // In order to bind the buffers, we need to know the names of the input and output tensors. 259 | // Note that indices are guaranteed to be less than IEngine::getNbBindings() 260 | const int inputIndex = engine.getBindingIndex(INPUT_BLOB_NAME); 261 | const int outputIndex = engine.getBindingIndex(OUTPUT_BLOB_NAME); 262 | context.setBindingDimensions(inputIndex, Dims4(1, 3, h_scale, w_scale)); 263 | 264 | // Create GPU buffers on device 265 | CHECK(cudaMalloc(&buffers[inputIndex], 3 * h_scale * w_scale * sizeof(float))); 266 | CHECK(cudaMalloc(&buffers[outputIndex], 2 * h_scale * w_scale * sizeof(float))); 267 | 268 | // Create stream 269 | cudaStream_t stream; 270 | CHECK(cudaStreamCreate(&stream)); 271 | 272 | // DMA input batch data to device, infer on the batch asynchronously, and DMA output back to host 273 | CHECK(cudaMemcpyAsync(buffers[inputIndex], input, 3 * h_scale * w_scale * sizeof(float), cudaMemcpyHostToDevice, stream)); 274 | context.enqueueV2(buffers, stream, nullptr); 275 | CHECK(cudaMemcpyAsync(output, buffers[outputIndex], h_scale * w_scale * 2 * sizeof(float), cudaMemcpyDeviceToHost, stream)); 276 | cudaStreamSynchronize(stream); 277 | 278 | // Release stream and buffers 279 | cudaStreamDestroy(stream); 280 | CHECK(cudaFree(buffers[inputIndex])); 281 | CHECK(cudaFree(buffers[outputIndex])); 282 | } 283 | 284 | int main(int argc, char** argv) { 285 | cudaSetDevice(DEVICE); 286 | // create a model using the API directly and serialize it to a stream 287 | char *trtModelStream{ nullptr }; 288 | size_t size{ 0 }; 289 | 290 | if (argc == 2 && std::string(argv[1]) == "-s") { 291 | IHostMemory* modelStream{ nullptr }; 292 | APIToModel(1, &modelStream); 293 | assert(modelStream != nullptr); 294 | std::ofstream p("DBNet.engine", std::ios::binary); 295 | if (!p) { 296 | std::cerr << "could not open plan output file" << std::endl; 297 | return -1; 298 | } 299 | p.write(reinterpret_cast(modelStream->data()), modelStream->size()); 300 | modelStream->destroy(); 301 | return 0; 302 | } 303 | else if (argc == 3 && std::string(argv[1]) == "-d") { 304 | std::ifstream file("DBNet.engine", std::ios::binary); 305 | if (file.good()) { 306 | file.seekg(0, file.end); 307 | size = file.tellg(); 308 | file.seekg(0, file.beg); 309 | trtModelStream = new char[size]; 310 | assert(trtModelStream); 311 | file.read(trtModelStream, size); 312 | file.close(); 313 | } 314 | } 315 | else { 316 | std::cerr << "arguments not right!" << std::endl; 317 | std::cerr << "./debnet -s // serialize model to plan file" << std::endl; 318 | std::cerr << "./debnet -d ../samples // deserialize plan file and run inference" << std::endl; 319 | return -1; 320 | } 321 | 322 | // prepare input data --------------------------- 323 | IRuntime* runtime = createInferRuntime(gLogger); 324 | assert(runtime != nullptr); 325 | ICudaEngine* engine = runtime->deserializeCudaEngine(trtModelStream, size); 326 | assert(engine != nullptr); 327 | IExecutionContext* context = engine->createExecutionContext(); 328 | assert(context != nullptr); 329 | delete[] trtModelStream; 330 | 331 | std::vector file_names; 332 | if (read_files_in_dir(argv[2], file_names) < 0) { 333 | std::cout << "read_files_in_dir failed." << std::endl; 334 | return -1; 335 | } 336 | 337 | std::vector mean_value{ 0.406, 0.456, 0.485 }; // BGR 338 | std::vector std_value{ 0.225, 0.224, 0.229 }; 339 | int fcount = 0; 340 | for (auto f : file_names) { 341 | fcount++; 342 | std::cout << fcount << " " << f << std::endl; 343 | cv::Mat pr_img = cv::imread(std::string(argv[2]) + "/" + f); 344 | cv::Mat src_img = pr_img.clone(); 345 | if (pr_img.empty()) continue; 346 | float scale = paddimg(pr_img, SHORT_INPUT); 347 | std::cout << "letterbox shape: " << pr_img.cols << ", " << pr_img.rows << std::endl; 348 | if (pr_img.cols < MIN_INPUT_SIZE || pr_img.rows < MIN_INPUT_SIZE) continue; 349 | float* data = new float[3 * pr_img.rows * pr_img.cols]; 350 | int i = 0; 351 | for (int row = 0; row < pr_img.rows; ++row) { 352 | uchar* uc_pixel = pr_img.data + row * pr_img.step; 353 | for (int col = 0; col < pr_img.cols; ++col) { 354 | data[i] = (uc_pixel[2] / 255.0 - mean_value[2]) / std_value[2]; 355 | data[i + pr_img.rows * pr_img.cols] = (uc_pixel[1] / 255.0 - mean_value[1]) / std_value[1]; 356 | data[i + 2 * pr_img.rows * pr_img.cols] = (uc_pixel[0] / 255.0 - mean_value[0]) / std_value[0]; 357 | uc_pixel += 3; 358 | ++i; 359 | } 360 | } 361 | 362 | float* prob = new float[pr_img.rows *pr_img.cols * 2]; 363 | // Run inference 364 | auto start = std::chrono::system_clock::now(); 365 | doInference(*context, data, prob, pr_img.rows, pr_img.cols); 366 | auto end = std::chrono::system_clock::now(); 367 | std::cout << std::chrono::duration_cast(end - start).count() << "ms" << std::endl; 368 | 369 | // prob 为 2* 640*640 拿出第一个 370 | cv::Mat map = cv::Mat::zeros(cv::Size(pr_img.cols, pr_img.rows), CV_8UC1); 371 | for (int h = 0; h < pr_img.rows; ++h) { 372 | uchar *ptr = map.ptr(h); 373 | for (int w = 0; w < pr_img.cols; ++w) { 374 | ptr[w] = (prob[h * pr_img.cols + w] > 0.3) ? 255 : 0; 375 | } 376 | } 377 | // 提取最小外接矩形 378 | std::vector> contours; 379 | std::vector hierarcy; 380 | cv::findContours(map, contours, hierarcy, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); 381 | 382 | std::vector boundRect(contours.size()); 383 | std::vector box(contours.size()); 384 | cv::Point2f rect[4]; 385 | for (int i = 0; i < contours.size(); i++) { 386 | box[i] = cv::minAreaRect(cv::Mat(contours[i])); 387 | //boundRect[i] = cv::boundingRect(cv::Mat(contours[i])); 388 | //绘制外接矩形和 最小外接矩形(for循环) 389 | //cv::rectangle(img, cv::Point(boundRect[i].x, boundRect[i].y), cv::Point(boundRect[i].x + boundRect[i].width, boundRect[i].y + boundRect[i].height), cv::Scalar(0, 255, 0), 2, 8); 390 | cv::RotatedRect expandbox = expandBox(box[i], EXPANDRATIO); 391 | expandbox.points(rect);//把最小外接矩形四个端点复制给rect数组 392 | for (int j = 0; j < 4; j++) { 393 | cv::Point2f p1, p2; 394 | p1.x = round(rect[j].x / pr_img.cols * src_img.cols); 395 | p1.y = round(rect[j].y / pr_img.rows * src_img.rows); 396 | p2.x = round(rect[(j + 1) % 4].x / pr_img.cols * src_img.cols); 397 | p2.y = round(rect[(j + 1) % 4].y / pr_img.rows * src_img.rows); 398 | cv::line(src_img, p1, p2, cv::Scalar(0, 0, 255), 2, 8); 399 | } 400 | } 401 | 402 | cv::imwrite("_" + f, src_img); 403 | //cv::waitKey(0); 404 | 405 | delete prob; 406 | delete data; 407 | } 408 | 409 | return 0; 410 | } -------------------------------------------------------------------------------- /include/dirent.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Dirent interface for Microsoft Visual Studio 3 | * 4 | * Copyright (C) 1998-2019 Toni Ronkko 5 | * This file is part of dirent. Dirent may be freely distributed 6 | * under the MIT license. For all details and documentation, see 7 | * https://github.com/tronkko/dirent 8 | */ 9 | #ifndef DIRENT_H 10 | #define DIRENT_H 11 | 12 | /* Hide warnings about unreferenced local functions */ 13 | #if defined(__clang__) 14 | # pragma clang diagnostic ignored "-Wunused-function" 15 | #elif defined(_MSC_VER) 16 | # pragma warning(disable:4505) 17 | #elif defined(__GNUC__) 18 | # pragma GCC diagnostic ignored "-Wunused-function" 19 | #endif 20 | 21 | /* 22 | * Include windows.h without Windows Sockets 1.1 to prevent conflicts with 23 | * Windows Sockets 2.0. 24 | */ 25 | #ifndef WIN32_LEAN_AND_MEAN 26 | # define WIN32_LEAN_AND_MEAN 27 | #endif 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | /* Indicates that d_type field is available in dirent structure */ 41 | #define _DIRENT_HAVE_D_TYPE 42 | 43 | /* Indicates that d_namlen field is available in dirent structure */ 44 | #define _DIRENT_HAVE_D_NAMLEN 45 | 46 | /* Entries missing from MSVC 6.0 */ 47 | #if !defined(FILE_ATTRIBUTE_DEVICE) 48 | # define FILE_ATTRIBUTE_DEVICE 0x40 49 | #endif 50 | 51 | /* File type and permission flags for stat(), general mask */ 52 | #if !defined(S_IFMT) 53 | # define S_IFMT _S_IFMT 54 | #endif 55 | 56 | /* Directory bit */ 57 | #if !defined(S_IFDIR) 58 | # define S_IFDIR _S_IFDIR 59 | #endif 60 | 61 | /* Character device bit */ 62 | #if !defined(S_IFCHR) 63 | # define S_IFCHR _S_IFCHR 64 | #endif 65 | 66 | /* Pipe bit */ 67 | #if !defined(S_IFFIFO) 68 | # define S_IFFIFO _S_IFFIFO 69 | #endif 70 | 71 | /* Regular file bit */ 72 | #if !defined(S_IFREG) 73 | # define S_IFREG _S_IFREG 74 | #endif 75 | 76 | /* Read permission */ 77 | #if !defined(S_IREAD) 78 | # define S_IREAD _S_IREAD 79 | #endif 80 | 81 | /* Write permission */ 82 | #if !defined(S_IWRITE) 83 | # define S_IWRITE _S_IWRITE 84 | #endif 85 | 86 | /* Execute permission */ 87 | #if !defined(S_IEXEC) 88 | # define S_IEXEC _S_IEXEC 89 | #endif 90 | 91 | /* Pipe */ 92 | #if !defined(S_IFIFO) 93 | # define S_IFIFO _S_IFIFO 94 | #endif 95 | 96 | /* Block device */ 97 | #if !defined(S_IFBLK) 98 | # define S_IFBLK 0 99 | #endif 100 | 101 | /* Link */ 102 | #if !defined(S_IFLNK) 103 | # define S_IFLNK 0 104 | #endif 105 | 106 | /* Socket */ 107 | #if !defined(S_IFSOCK) 108 | # define S_IFSOCK 0 109 | #endif 110 | 111 | /* Read user permission */ 112 | #if !defined(S_IRUSR) 113 | # define S_IRUSR S_IREAD 114 | #endif 115 | 116 | /* Write user permission */ 117 | #if !defined(S_IWUSR) 118 | # define S_IWUSR S_IWRITE 119 | #endif 120 | 121 | /* Execute user permission */ 122 | #if !defined(S_IXUSR) 123 | # define S_IXUSR 0 124 | #endif 125 | 126 | /* Read group permission */ 127 | #if !defined(S_IRGRP) 128 | # define S_IRGRP 0 129 | #endif 130 | 131 | /* Write group permission */ 132 | #if !defined(S_IWGRP) 133 | # define S_IWGRP 0 134 | #endif 135 | 136 | /* Execute group permission */ 137 | #if !defined(S_IXGRP) 138 | # define S_IXGRP 0 139 | #endif 140 | 141 | /* Read others permission */ 142 | #if !defined(S_IROTH) 143 | # define S_IROTH 0 144 | #endif 145 | 146 | /* Write others permission */ 147 | #if !defined(S_IWOTH) 148 | # define S_IWOTH 0 149 | #endif 150 | 151 | /* Execute others permission */ 152 | #if !defined(S_IXOTH) 153 | # define S_IXOTH 0 154 | #endif 155 | 156 | /* Maximum length of file name */ 157 | #if !defined(PATH_MAX) 158 | # define PATH_MAX MAX_PATH 159 | #endif 160 | #if !defined(FILENAME_MAX) 161 | # define FILENAME_MAX MAX_PATH 162 | #endif 163 | #if !defined(NAME_MAX) 164 | # define NAME_MAX FILENAME_MAX 165 | #endif 166 | 167 | /* File type flags for d_type */ 168 | #define DT_UNKNOWN 0 169 | #define DT_REG S_IFREG 170 | #define DT_DIR S_IFDIR 171 | #define DT_FIFO S_IFIFO 172 | #define DT_SOCK S_IFSOCK 173 | #define DT_CHR S_IFCHR 174 | #define DT_BLK S_IFBLK 175 | #define DT_LNK S_IFLNK 176 | 177 | /* Macros for converting between st_mode and d_type */ 178 | #define IFTODT(mode) ((mode) & S_IFMT) 179 | #define DTTOIF(type) (type) 180 | 181 | /* 182 | * File type macros. Note that block devices, sockets and links cannot be 183 | * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are 184 | * only defined for compatibility. These macros should always return false 185 | * on Windows. 186 | */ 187 | #if !defined(S_ISFIFO) 188 | # define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) 189 | #endif 190 | #if !defined(S_ISDIR) 191 | # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) 192 | #endif 193 | #if !defined(S_ISREG) 194 | # define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) 195 | #endif 196 | #if !defined(S_ISLNK) 197 | # define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) 198 | #endif 199 | #if !defined(S_ISSOCK) 200 | # define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) 201 | #endif 202 | #if !defined(S_ISCHR) 203 | # define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) 204 | #endif 205 | #if !defined(S_ISBLK) 206 | # define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) 207 | #endif 208 | 209 | /* Return the exact length of the file name without zero terminator */ 210 | #define _D_EXACT_NAMLEN(p) ((p)->d_namlen) 211 | 212 | /* Return the maximum size of a file name */ 213 | #define _D_ALLOC_NAMLEN(p) ((PATH_MAX)+1) 214 | 215 | 216 | #ifdef __cplusplus 217 | extern "C" { 218 | #endif 219 | 220 | 221 | /* Wide-character version */ 222 | struct _wdirent { 223 | /* Always zero */ 224 | long d_ino; 225 | 226 | /* File position within stream */ 227 | long d_off; 228 | 229 | /* Structure size */ 230 | unsigned short d_reclen; 231 | 232 | /* Length of name without \0 */ 233 | size_t d_namlen; 234 | 235 | /* File type */ 236 | int d_type; 237 | 238 | /* File name */ 239 | wchar_t d_name[PATH_MAX+1]; 240 | }; 241 | typedef struct _wdirent _wdirent; 242 | 243 | struct _WDIR { 244 | /* Current directory entry */ 245 | struct _wdirent ent; 246 | 247 | /* Private file data */ 248 | WIN32_FIND_DATAW data; 249 | 250 | /* True if data is valid */ 251 | int cached; 252 | 253 | /* Win32 search handle */ 254 | HANDLE handle; 255 | 256 | /* Initial directory name */ 257 | wchar_t *patt; 258 | }; 259 | typedef struct _WDIR _WDIR; 260 | 261 | /* Multi-byte character version */ 262 | struct dirent { 263 | /* Always zero */ 264 | long d_ino; 265 | 266 | /* File position within stream */ 267 | long d_off; 268 | 269 | /* Structure size */ 270 | unsigned short d_reclen; 271 | 272 | /* Length of name without \0 */ 273 | size_t d_namlen; 274 | 275 | /* File type */ 276 | int d_type; 277 | 278 | /* File name */ 279 | char d_name[PATH_MAX+1]; 280 | }; 281 | typedef struct dirent dirent; 282 | 283 | struct DIR { 284 | struct dirent ent; 285 | struct _WDIR *wdirp; 286 | }; 287 | typedef struct DIR DIR; 288 | 289 | 290 | /* Dirent functions */ 291 | static DIR *opendir (const char *dirname); 292 | static _WDIR *_wopendir (const wchar_t *dirname); 293 | 294 | static struct dirent *readdir (DIR *dirp); 295 | static struct _wdirent *_wreaddir (_WDIR *dirp); 296 | 297 | static int readdir_r( 298 | DIR *dirp, struct dirent *entry, struct dirent **result); 299 | static int _wreaddir_r( 300 | _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result); 301 | 302 | static int closedir (DIR *dirp); 303 | static int _wclosedir (_WDIR *dirp); 304 | 305 | static void rewinddir (DIR* dirp); 306 | static void _wrewinddir (_WDIR* dirp); 307 | 308 | static int scandir (const char *dirname, struct dirent ***namelist, 309 | int (*filter)(const struct dirent*), 310 | int (*compare)(const struct dirent**, const struct dirent**)); 311 | 312 | static int alphasort (const struct dirent **a, const struct dirent **b); 313 | 314 | static int versionsort (const struct dirent **a, const struct dirent **b); 315 | 316 | 317 | /* For compatibility with Symbian */ 318 | #define wdirent _wdirent 319 | #define WDIR _WDIR 320 | #define wopendir _wopendir 321 | #define wreaddir _wreaddir 322 | #define wclosedir _wclosedir 323 | #define wrewinddir _wrewinddir 324 | 325 | 326 | /* Internal utility functions */ 327 | static WIN32_FIND_DATAW *dirent_first (_WDIR *dirp); 328 | static WIN32_FIND_DATAW *dirent_next (_WDIR *dirp); 329 | 330 | static int dirent_mbstowcs_s( 331 | size_t *pReturnValue, 332 | wchar_t *wcstr, 333 | size_t sizeInWords, 334 | const char *mbstr, 335 | size_t count); 336 | 337 | static int dirent_wcstombs_s( 338 | size_t *pReturnValue, 339 | char *mbstr, 340 | size_t sizeInBytes, 341 | const wchar_t *wcstr, 342 | size_t count); 343 | 344 | static void dirent_set_errno (int error); 345 | 346 | 347 | /* 348 | * Open directory stream DIRNAME for read and return a pointer to the 349 | * internal working area that is used to retrieve individual directory 350 | * entries. 351 | */ 352 | static _WDIR* 353 | _wopendir( 354 | const wchar_t *dirname) 355 | { 356 | _WDIR *dirp; 357 | DWORD n; 358 | wchar_t *p; 359 | 360 | /* Must have directory name */ 361 | if (dirname == NULL || dirname[0] == '\0') { 362 | dirent_set_errno (ENOENT); 363 | return NULL; 364 | } 365 | 366 | /* Allocate new _WDIR structure */ 367 | dirp = (_WDIR*) malloc (sizeof (struct _WDIR)); 368 | if (!dirp) { 369 | return NULL; 370 | } 371 | 372 | /* Reset _WDIR structure */ 373 | dirp->handle = INVALID_HANDLE_VALUE; 374 | dirp->patt = NULL; 375 | dirp->cached = 0; 376 | 377 | /* 378 | * Compute the length of full path plus zero terminator 379 | * 380 | * Note that on WinRT there's no way to convert relative paths 381 | * into absolute paths, so just assume it is an absolute path. 382 | */ 383 | #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) 384 | /* Desktop */ 385 | n = GetFullPathNameW (dirname, 0, NULL, NULL); 386 | #else 387 | /* WinRT */ 388 | n = wcslen (dirname); 389 | #endif 390 | 391 | /* Allocate room for absolute directory name and search pattern */ 392 | dirp->patt = (wchar_t*) malloc (sizeof (wchar_t) * n + 16); 393 | if (dirp->patt == NULL) { 394 | goto exit_closedir; 395 | } 396 | 397 | /* 398 | * Convert relative directory name to an absolute one. This 399 | * allows rewinddir() to function correctly even when current 400 | * working directory is changed between opendir() and rewinddir(). 401 | * 402 | * Note that on WinRT there's no way to convert relative paths 403 | * into absolute paths, so just assume it is an absolute path. 404 | */ 405 | #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) 406 | /* Desktop */ 407 | n = GetFullPathNameW (dirname, n, dirp->patt, NULL); 408 | if (n <= 0) { 409 | goto exit_closedir; 410 | } 411 | #else 412 | /* WinRT */ 413 | wcsncpy_s (dirp->patt, n+1, dirname, n); 414 | #endif 415 | 416 | /* Append search pattern \* to the directory name */ 417 | p = dirp->patt + n; 418 | switch (p[-1]) { 419 | case '\\': 420 | case '/': 421 | case ':': 422 | /* Directory ends in path separator, e.g. c:\temp\ */ 423 | /*NOP*/; 424 | break; 425 | 426 | default: 427 | /* Directory name doesn't end in path separator */ 428 | *p++ = '\\'; 429 | } 430 | *p++ = '*'; 431 | *p = '\0'; 432 | 433 | /* Open directory stream and retrieve the first entry */ 434 | if (!dirent_first (dirp)) { 435 | goto exit_closedir; 436 | } 437 | 438 | /* Success */ 439 | return dirp; 440 | 441 | /* Failure */ 442 | exit_closedir: 443 | _wclosedir (dirp); 444 | return NULL; 445 | } 446 | 447 | /* 448 | * Read next directory entry. 449 | * 450 | * Returns pointer to static directory entry which may be overwritten by 451 | * subsequent calls to _wreaddir(). 452 | */ 453 | static struct _wdirent* 454 | _wreaddir( 455 | _WDIR *dirp) 456 | { 457 | struct _wdirent *entry; 458 | 459 | /* 460 | * Read directory entry to buffer. We can safely ignore the return value 461 | * as entry will be set to NULL in case of error. 462 | */ 463 | (void) _wreaddir_r (dirp, &dirp->ent, &entry); 464 | 465 | /* Return pointer to statically allocated directory entry */ 466 | return entry; 467 | } 468 | 469 | /* 470 | * Read next directory entry. 471 | * 472 | * Returns zero on success. If end of directory stream is reached, then sets 473 | * result to NULL and returns zero. 474 | */ 475 | static int 476 | _wreaddir_r( 477 | _WDIR *dirp, 478 | struct _wdirent *entry, 479 | struct _wdirent **result) 480 | { 481 | WIN32_FIND_DATAW *datap; 482 | 483 | /* Read next directory entry */ 484 | datap = dirent_next (dirp); 485 | if (datap) { 486 | size_t n; 487 | DWORD attr; 488 | 489 | /* 490 | * Copy file name as wide-character string. If the file name is too 491 | * long to fit in to the destination buffer, then truncate file name 492 | * to PATH_MAX characters and zero-terminate the buffer. 493 | */ 494 | n = 0; 495 | while (n < PATH_MAX && datap->cFileName[n] != 0) { 496 | entry->d_name[n] = datap->cFileName[n]; 497 | n++; 498 | } 499 | entry->d_name[n] = 0; 500 | 501 | /* Length of file name excluding zero terminator */ 502 | entry->d_namlen = n; 503 | 504 | /* File type */ 505 | attr = datap->dwFileAttributes; 506 | if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { 507 | entry->d_type = DT_CHR; 508 | } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { 509 | entry->d_type = DT_DIR; 510 | } else { 511 | entry->d_type = DT_REG; 512 | } 513 | 514 | /* Reset dummy fields */ 515 | entry->d_ino = 0; 516 | entry->d_off = 0; 517 | entry->d_reclen = sizeof (struct _wdirent); 518 | 519 | /* Set result address */ 520 | *result = entry; 521 | 522 | } else { 523 | 524 | /* Return NULL to indicate end of directory */ 525 | *result = NULL; 526 | 527 | } 528 | 529 | return /*OK*/0; 530 | } 531 | 532 | /* 533 | * Close directory stream opened by opendir() function. This invalidates the 534 | * DIR structure as well as any directory entry read previously by 535 | * _wreaddir(). 536 | */ 537 | static int 538 | _wclosedir( 539 | _WDIR *dirp) 540 | { 541 | int ok; 542 | if (dirp) { 543 | 544 | /* Release search handle */ 545 | if (dirp->handle != INVALID_HANDLE_VALUE) { 546 | FindClose (dirp->handle); 547 | } 548 | 549 | /* Release search pattern */ 550 | free (dirp->patt); 551 | 552 | /* Release directory structure */ 553 | free (dirp); 554 | ok = /*success*/0; 555 | 556 | } else { 557 | 558 | /* Invalid directory stream */ 559 | dirent_set_errno (EBADF); 560 | ok = /*failure*/-1; 561 | 562 | } 563 | return ok; 564 | } 565 | 566 | /* 567 | * Rewind directory stream such that _wreaddir() returns the very first 568 | * file name again. 569 | */ 570 | static void 571 | _wrewinddir( 572 | _WDIR* dirp) 573 | { 574 | if (dirp) { 575 | /* Release existing search handle */ 576 | if (dirp->handle != INVALID_HANDLE_VALUE) { 577 | FindClose (dirp->handle); 578 | } 579 | 580 | /* Open new search handle */ 581 | dirent_first (dirp); 582 | } 583 | } 584 | 585 | /* Get first directory entry (internal) */ 586 | static WIN32_FIND_DATAW* 587 | dirent_first( 588 | _WDIR *dirp) 589 | { 590 | WIN32_FIND_DATAW *datap; 591 | DWORD error; 592 | 593 | /* Open directory and retrieve the first entry */ 594 | dirp->handle = FindFirstFileExW( 595 | dirp->patt, FindExInfoStandard, &dirp->data, 596 | FindExSearchNameMatch, NULL, 0); 597 | if (dirp->handle != INVALID_HANDLE_VALUE) { 598 | 599 | /* a directory entry is now waiting in memory */ 600 | datap = &dirp->data; 601 | dirp->cached = 1; 602 | 603 | } else { 604 | 605 | /* Failed to open directory: no directory entry in memory */ 606 | dirp->cached = 0; 607 | datap = NULL; 608 | 609 | /* Set error code */ 610 | error = GetLastError (); 611 | switch (error) { 612 | case ERROR_ACCESS_DENIED: 613 | /* No read access to directory */ 614 | dirent_set_errno (EACCES); 615 | break; 616 | 617 | case ERROR_DIRECTORY: 618 | /* Directory name is invalid */ 619 | dirent_set_errno (ENOTDIR); 620 | break; 621 | 622 | case ERROR_PATH_NOT_FOUND: 623 | default: 624 | /* Cannot find the file */ 625 | dirent_set_errno (ENOENT); 626 | } 627 | 628 | } 629 | return datap; 630 | } 631 | 632 | /* 633 | * Get next directory entry (internal). 634 | * 635 | * Returns 636 | */ 637 | static WIN32_FIND_DATAW* 638 | dirent_next( 639 | _WDIR *dirp) 640 | { 641 | WIN32_FIND_DATAW *p; 642 | 643 | /* Get next directory entry */ 644 | if (dirp->cached != 0) { 645 | 646 | /* A valid directory entry already in memory */ 647 | p = &dirp->data; 648 | dirp->cached = 0; 649 | 650 | } else if (dirp->handle != INVALID_HANDLE_VALUE) { 651 | 652 | /* Get the next directory entry from stream */ 653 | if (FindNextFileW (dirp->handle, &dirp->data) != FALSE) { 654 | /* Got a file */ 655 | p = &dirp->data; 656 | } else { 657 | /* The very last entry has been processed or an error occurred */ 658 | FindClose (dirp->handle); 659 | dirp->handle = INVALID_HANDLE_VALUE; 660 | p = NULL; 661 | } 662 | 663 | } else { 664 | 665 | /* End of directory stream reached */ 666 | p = NULL; 667 | 668 | } 669 | 670 | return p; 671 | } 672 | 673 | /* 674 | * Open directory stream using plain old C-string. 675 | */ 676 | static DIR* 677 | opendir( 678 | const char *dirname) 679 | { 680 | struct DIR *dirp; 681 | 682 | /* Must have directory name */ 683 | if (dirname == NULL || dirname[0] == '\0') { 684 | dirent_set_errno (ENOENT); 685 | return NULL; 686 | } 687 | 688 | /* Allocate memory for DIR structure */ 689 | dirp = (DIR*) malloc (sizeof (struct DIR)); 690 | if (!dirp) { 691 | return NULL; 692 | } 693 | { 694 | int error; 695 | wchar_t wname[PATH_MAX + 1]; 696 | size_t n; 697 | 698 | /* Convert directory name to wide-character string */ 699 | error = dirent_mbstowcs_s( 700 | &n, wname, PATH_MAX + 1, dirname, PATH_MAX + 1); 701 | if (error) { 702 | /* 703 | * Cannot convert file name to wide-character string. This 704 | * occurs if the string contains invalid multi-byte sequences or 705 | * the output buffer is too small to contain the resulting 706 | * string. 707 | */ 708 | goto exit_free; 709 | } 710 | 711 | 712 | /* Open directory stream using wide-character name */ 713 | dirp->wdirp = _wopendir (wname); 714 | if (!dirp->wdirp) { 715 | goto exit_free; 716 | } 717 | 718 | } 719 | 720 | /* Success */ 721 | return dirp; 722 | 723 | /* Failure */ 724 | exit_free: 725 | free (dirp); 726 | return NULL; 727 | } 728 | 729 | /* 730 | * Read next directory entry. 731 | */ 732 | static struct dirent* 733 | readdir( 734 | DIR *dirp) 735 | { 736 | struct dirent *entry; 737 | 738 | /* 739 | * Read directory entry to buffer. We can safely ignore the return value 740 | * as entry will be set to NULL in case of error. 741 | */ 742 | (void) readdir_r (dirp, &dirp->ent, &entry); 743 | 744 | /* Return pointer to statically allocated directory entry */ 745 | return entry; 746 | } 747 | 748 | /* 749 | * Read next directory entry into called-allocated buffer. 750 | * 751 | * Returns zero on success. If the end of directory stream is reached, then 752 | * sets result to NULL and returns zero. 753 | */ 754 | static int 755 | readdir_r( 756 | DIR *dirp, 757 | struct dirent *entry, 758 | struct dirent **result) 759 | { 760 | WIN32_FIND_DATAW *datap; 761 | 762 | /* Read next directory entry */ 763 | datap = dirent_next (dirp->wdirp); 764 | if (datap) { 765 | size_t n; 766 | int error; 767 | 768 | /* Attempt to convert file name to multi-byte string */ 769 | error = dirent_wcstombs_s( 770 | &n, entry->d_name, PATH_MAX + 1, datap->cFileName, PATH_MAX + 1); 771 | 772 | /* 773 | * If the file name cannot be represented by a multi-byte string, 774 | * then attempt to use old 8+3 file name. This allows traditional 775 | * Unix-code to access some file names despite of unicode 776 | * characters, although file names may seem unfamiliar to the user. 777 | * 778 | * Be ware that the code below cannot come up with a short file 779 | * name unless the file system provides one. At least 780 | * VirtualBox shared folders fail to do this. 781 | */ 782 | if (error && datap->cAlternateFileName[0] != '\0') { 783 | error = dirent_wcstombs_s( 784 | &n, entry->d_name, PATH_MAX + 1, 785 | datap->cAlternateFileName, PATH_MAX + 1); 786 | } 787 | 788 | if (!error) { 789 | DWORD attr; 790 | 791 | /* Length of file name excluding zero terminator */ 792 | entry->d_namlen = n - 1; 793 | 794 | /* File attributes */ 795 | attr = datap->dwFileAttributes; 796 | if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { 797 | entry->d_type = DT_CHR; 798 | } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { 799 | entry->d_type = DT_DIR; 800 | } else { 801 | entry->d_type = DT_REG; 802 | } 803 | 804 | /* Reset dummy fields */ 805 | entry->d_ino = 0; 806 | entry->d_off = 0; 807 | entry->d_reclen = sizeof (struct dirent); 808 | 809 | } else { 810 | 811 | /* 812 | * Cannot convert file name to multi-byte string so construct 813 | * an erroneous directory entry and return that. Note that 814 | * we cannot return NULL as that would stop the processing 815 | * of directory entries completely. 816 | */ 817 | entry->d_name[0] = '?'; 818 | entry->d_name[1] = '\0'; 819 | entry->d_namlen = 1; 820 | entry->d_type = DT_UNKNOWN; 821 | entry->d_ino = 0; 822 | entry->d_off = -1; 823 | entry->d_reclen = 0; 824 | 825 | } 826 | 827 | /* Return pointer to directory entry */ 828 | *result = entry; 829 | 830 | } else { 831 | 832 | /* No more directory entries */ 833 | *result = NULL; 834 | 835 | } 836 | 837 | return /*OK*/0; 838 | } 839 | 840 | /* 841 | * Close directory stream. 842 | */ 843 | static int 844 | closedir( 845 | DIR *dirp) 846 | { 847 | int ok; 848 | if (dirp) { 849 | 850 | /* Close wide-character directory stream */ 851 | ok = _wclosedir (dirp->wdirp); 852 | dirp->wdirp = NULL; 853 | 854 | /* Release multi-byte character version */ 855 | free (dirp); 856 | 857 | } else { 858 | 859 | /* Invalid directory stream */ 860 | dirent_set_errno (EBADF); 861 | ok = /*failure*/-1; 862 | 863 | } 864 | return ok; 865 | } 866 | 867 | /* 868 | * Rewind directory stream to beginning. 869 | */ 870 | static void 871 | rewinddir( 872 | DIR* dirp) 873 | { 874 | /* Rewind wide-character string directory stream */ 875 | _wrewinddir (dirp->wdirp); 876 | } 877 | 878 | /* 879 | * Scan directory for entries. 880 | */ 881 | static int 882 | scandir( 883 | const char *dirname, 884 | struct dirent ***namelist, 885 | int (*filter)(const struct dirent*), 886 | int (*compare)(const struct dirent**, const struct dirent**)) 887 | { 888 | struct dirent **files = NULL; 889 | size_t size = 0; 890 | size_t allocated = 0; 891 | const size_t init_size = 1; 892 | DIR *dir = NULL; 893 | struct dirent *entry; 894 | struct dirent *tmp = NULL; 895 | size_t i; 896 | int result = 0; 897 | 898 | /* Open directory stream */ 899 | dir = opendir (dirname); 900 | if (dir) { 901 | 902 | /* Read directory entries to memory */ 903 | while (1) { 904 | 905 | /* Enlarge pointer table to make room for another pointer */ 906 | if (size >= allocated) { 907 | void *p; 908 | size_t num_entries; 909 | 910 | /* Compute number of entries in the enlarged pointer table */ 911 | if (size < init_size) { 912 | /* Allocate initial pointer table */ 913 | num_entries = init_size; 914 | } else { 915 | /* Double the size */ 916 | num_entries = size * 2; 917 | } 918 | 919 | /* Allocate first pointer table or enlarge existing table */ 920 | p = realloc (files, sizeof (void*) * num_entries); 921 | if (p != NULL) { 922 | /* Got the memory */ 923 | files = (dirent**) p; 924 | allocated = num_entries; 925 | } else { 926 | /* Out of memory */ 927 | result = -1; 928 | break; 929 | } 930 | 931 | } 932 | 933 | /* Allocate room for temporary directory entry */ 934 | if (tmp == NULL) { 935 | tmp = (struct dirent*) malloc (sizeof (struct dirent)); 936 | if (tmp == NULL) { 937 | /* Cannot allocate temporary directory entry */ 938 | result = -1; 939 | break; 940 | } 941 | } 942 | 943 | /* Read directory entry to temporary area */ 944 | if (readdir_r (dir, tmp, &entry) == /*OK*/0) { 945 | 946 | /* Did we get an entry? */ 947 | if (entry != NULL) { 948 | int pass; 949 | 950 | /* Determine whether to include the entry in result */ 951 | if (filter) { 952 | /* Let the filter function decide */ 953 | pass = filter (tmp); 954 | } else { 955 | /* No filter function, include everything */ 956 | pass = 1; 957 | } 958 | 959 | if (pass) { 960 | /* Store the temporary entry to pointer table */ 961 | files[size++] = tmp; 962 | tmp = NULL; 963 | 964 | /* Keep up with the number of files */ 965 | result++; 966 | } 967 | 968 | } else { 969 | 970 | /* 971 | * End of directory stream reached => sort entries and 972 | * exit. 973 | */ 974 | qsort (files, size, sizeof (void*), 975 | (int (*) (const void*, const void*)) compare); 976 | break; 977 | 978 | } 979 | 980 | } else { 981 | /* Error reading directory entry */ 982 | result = /*Error*/ -1; 983 | break; 984 | } 985 | 986 | } 987 | 988 | } else { 989 | /* Cannot open directory */ 990 | result = /*Error*/ -1; 991 | } 992 | 993 | /* Release temporary directory entry */ 994 | free (tmp); 995 | 996 | /* Release allocated memory on error */ 997 | if (result < 0) { 998 | for (i = 0; i < size; i++) { 999 | free (files[i]); 1000 | } 1001 | free (files); 1002 | files = NULL; 1003 | } 1004 | 1005 | /* Close directory stream */ 1006 | if (dir) { 1007 | closedir (dir); 1008 | } 1009 | 1010 | /* Pass pointer table to caller */ 1011 | if (namelist) { 1012 | *namelist = files; 1013 | } 1014 | return result; 1015 | } 1016 | 1017 | /* Alphabetical sorting */ 1018 | static int 1019 | alphasort( 1020 | const struct dirent **a, const struct dirent **b) 1021 | { 1022 | return strcoll ((*a)->d_name, (*b)->d_name); 1023 | } 1024 | 1025 | /* Sort versions */ 1026 | static int 1027 | versionsort( 1028 | const struct dirent **a, const struct dirent **b) 1029 | { 1030 | /* FIXME: implement strverscmp and use that */ 1031 | return alphasort (a, b); 1032 | } 1033 | 1034 | /* Convert multi-byte string to wide character string */ 1035 | static int 1036 | dirent_mbstowcs_s( 1037 | size_t *pReturnValue, 1038 | wchar_t *wcstr, 1039 | size_t sizeInWords, 1040 | const char *mbstr, 1041 | size_t count) 1042 | { 1043 | int error; 1044 | 1045 | #if defined(_MSC_VER) && _MSC_VER >= 1400 1046 | 1047 | /* Microsoft Visual Studio 2005 or later */ 1048 | error = mbstowcs_s (pReturnValue, wcstr, sizeInWords, mbstr, count); 1049 | 1050 | #else 1051 | 1052 | /* Older Visual Studio or non-Microsoft compiler */ 1053 | size_t n; 1054 | 1055 | /* Convert to wide-character string (or count characters) */ 1056 | n = mbstowcs (wcstr, mbstr, sizeInWords); 1057 | if (!wcstr || n < count) { 1058 | 1059 | /* Zero-terminate output buffer */ 1060 | if (wcstr && sizeInWords) { 1061 | if (n >= sizeInWords) { 1062 | n = sizeInWords - 1; 1063 | } 1064 | wcstr[n] = 0; 1065 | } 1066 | 1067 | /* Length of resulting multi-byte string WITH zero terminator */ 1068 | if (pReturnValue) { 1069 | *pReturnValue = n + 1; 1070 | } 1071 | 1072 | /* Success */ 1073 | error = 0; 1074 | 1075 | } else { 1076 | 1077 | /* Could not convert string */ 1078 | error = 1; 1079 | 1080 | } 1081 | 1082 | #endif 1083 | return error; 1084 | } 1085 | 1086 | /* Convert wide-character string to multi-byte string */ 1087 | static int 1088 | dirent_wcstombs_s( 1089 | size_t *pReturnValue, 1090 | char *mbstr, 1091 | size_t sizeInBytes, /* max size of mbstr */ 1092 | const wchar_t *wcstr, 1093 | size_t count) 1094 | { 1095 | int error; 1096 | 1097 | #if defined(_MSC_VER) && _MSC_VER >= 1400 1098 | 1099 | /* Microsoft Visual Studio 2005 or later */ 1100 | error = wcstombs_s (pReturnValue, mbstr, sizeInBytes, wcstr, count); 1101 | 1102 | #else 1103 | 1104 | /* Older Visual Studio or non-Microsoft compiler */ 1105 | size_t n; 1106 | 1107 | /* Convert to multi-byte string (or count the number of bytes needed) */ 1108 | n = wcstombs (mbstr, wcstr, sizeInBytes); 1109 | if (!mbstr || n < count) { 1110 | 1111 | /* Zero-terminate output buffer */ 1112 | if (mbstr && sizeInBytes) { 1113 | if (n >= sizeInBytes) { 1114 | n = sizeInBytes - 1; 1115 | } 1116 | mbstr[n] = '\0'; 1117 | } 1118 | 1119 | /* Length of resulting multi-bytes string WITH zero-terminator */ 1120 | if (pReturnValue) { 1121 | *pReturnValue = n + 1; 1122 | } 1123 | 1124 | /* Success */ 1125 | error = 0; 1126 | 1127 | } else { 1128 | 1129 | /* Cannot convert string */ 1130 | error = 1; 1131 | 1132 | } 1133 | 1134 | #endif 1135 | return error; 1136 | } 1137 | 1138 | /* Set errno variable */ 1139 | static void 1140 | dirent_set_errno( 1141 | int error) 1142 | { 1143 | #if defined(_MSC_VER) && _MSC_VER >= 1400 1144 | 1145 | /* Microsoft Visual Studio 2005 and later */ 1146 | _set_errno (error); 1147 | 1148 | #else 1149 | 1150 | /* Non-Microsoft compiler or older Microsoft compiler */ 1151 | errno = error; 1152 | 1153 | #endif 1154 | } 1155 | 1156 | 1157 | #ifdef __cplusplus 1158 | } 1159 | #endif 1160 | #endif /*DIRENT_H*/ 1161 | -------------------------------------------------------------------------------- /logging.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef TENSORRT_LOGGING_H 18 | #define TENSORRT_LOGGING_H 19 | 20 | #include "NvInferRuntimeCommon.h" 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | using Severity = nvinfer1::ILogger::Severity; 30 | 31 | class LogStreamConsumerBuffer : public std::stringbuf 32 | { 33 | public: 34 | LogStreamConsumerBuffer(std::ostream& stream, const std::string& prefix, bool shouldLog) 35 | : mOutput(stream) 36 | , mPrefix(prefix) 37 | , mShouldLog(shouldLog) 38 | { 39 | } 40 | 41 | LogStreamConsumerBuffer(LogStreamConsumerBuffer&& other) 42 | : mOutput(other.mOutput) 43 | { 44 | } 45 | 46 | ~LogStreamConsumerBuffer() 47 | { 48 | // std::streambuf::pbase() gives a pointer to the beginning of the buffered part of the output sequence 49 | // std::streambuf::pptr() gives a pointer to the current position of the output sequence 50 | // if the pointer to the beginning is not equal to the pointer to the current position, 51 | // call putOutput() to log the output to the stream 52 | if (pbase() != pptr()) 53 | { 54 | putOutput(); 55 | } 56 | } 57 | 58 | // synchronizes the stream buffer and returns 0 on success 59 | // synchronizing the stream buffer consists of inserting the buffer contents into the stream, 60 | // resetting the buffer and flushing the stream 61 | virtual int sync() 62 | { 63 | putOutput(); 64 | return 0; 65 | } 66 | 67 | void putOutput() 68 | { 69 | if (mShouldLog) 70 | { 71 | // prepend timestamp 72 | std::time_t timestamp = std::time(nullptr); 73 | tm* tm_local = std::localtime(×tamp); 74 | std::cout << "["; 75 | std::cout << std::setw(2) << std::setfill('0') << 1 + tm_local->tm_mon << "/"; 76 | std::cout << std::setw(2) << std::setfill('0') << tm_local->tm_mday << "/"; 77 | std::cout << std::setw(4) << std::setfill('0') << 1900 + tm_local->tm_year << "-"; 78 | std::cout << std::setw(2) << std::setfill('0') << tm_local->tm_hour << ":"; 79 | std::cout << std::setw(2) << std::setfill('0') << tm_local->tm_min << ":"; 80 | std::cout << std::setw(2) << std::setfill('0') << tm_local->tm_sec << "] "; 81 | // std::stringbuf::str() gets the string contents of the buffer 82 | // insert the buffer contents pre-appended by the appropriate prefix into the stream 83 | mOutput << mPrefix << str(); 84 | // set the buffer to empty 85 | str(""); 86 | // flush the stream 87 | mOutput.flush(); 88 | } 89 | } 90 | 91 | void setShouldLog(bool shouldLog) 92 | { 93 | mShouldLog = shouldLog; 94 | } 95 | 96 | private: 97 | std::ostream& mOutput; 98 | std::string mPrefix; 99 | bool mShouldLog; 100 | }; 101 | 102 | //! 103 | //! \class LogStreamConsumerBase 104 | //! \brief Convenience object used to initialize LogStreamConsumerBuffer before std::ostream in LogStreamConsumer 105 | //! 106 | class LogStreamConsumerBase 107 | { 108 | public: 109 | LogStreamConsumerBase(std::ostream& stream, const std::string& prefix, bool shouldLog) 110 | : mBuffer(stream, prefix, shouldLog) 111 | { 112 | } 113 | 114 | protected: 115 | LogStreamConsumerBuffer mBuffer; 116 | }; 117 | 118 | //! 119 | //! \class LogStreamConsumer 120 | //! \brief Convenience object used to facilitate use of C++ stream syntax when logging messages. 121 | //! Order of base classes is LogStreamConsumerBase and then std::ostream. 122 | //! This is because the LogStreamConsumerBase class is used to initialize the LogStreamConsumerBuffer member field 123 | //! in LogStreamConsumer and then the address of the buffer is passed to std::ostream. 124 | //! This is necessary to prevent the address of an uninitialized buffer from being passed to std::ostream. 125 | //! Please do not change the order of the parent classes. 126 | //! 127 | class LogStreamConsumer : protected LogStreamConsumerBase, public std::ostream 128 | { 129 | public: 130 | //! \brief Creates a LogStreamConsumer which logs messages with level severity. 131 | //! Reportable severity determines if the messages are severe enough to be logged. 132 | LogStreamConsumer(Severity reportableSeverity, Severity severity) 133 | : LogStreamConsumerBase(severityOstream(severity), severityPrefix(severity), severity <= reportableSeverity) 134 | , std::ostream(&mBuffer) // links the stream buffer with the stream 135 | , mShouldLog(severity <= reportableSeverity) 136 | , mSeverity(severity) 137 | { 138 | } 139 | 140 | LogStreamConsumer(LogStreamConsumer&& other) 141 | : LogStreamConsumerBase(severityOstream(other.mSeverity), severityPrefix(other.mSeverity), other.mShouldLog) 142 | , std::ostream(&mBuffer) // links the stream buffer with the stream 143 | , mShouldLog(other.mShouldLog) 144 | , mSeverity(other.mSeverity) 145 | { 146 | } 147 | 148 | void setReportableSeverity(Severity reportableSeverity) 149 | { 150 | mShouldLog = mSeverity <= reportableSeverity; 151 | mBuffer.setShouldLog(mShouldLog); 152 | } 153 | 154 | private: 155 | static std::ostream& severityOstream(Severity severity) 156 | { 157 | return severity >= Severity::kINFO ? std::cout : std::cerr; 158 | } 159 | 160 | static std::string severityPrefix(Severity severity) 161 | { 162 | switch (severity) 163 | { 164 | case Severity::kINTERNAL_ERROR: return "[F] "; 165 | case Severity::kERROR: return "[E] "; 166 | case Severity::kWARNING: return "[W] "; 167 | case Severity::kINFO: return "[I] "; 168 | case Severity::kVERBOSE: return "[V] "; 169 | default: assert(0); return ""; 170 | } 171 | } 172 | 173 | bool mShouldLog; 174 | Severity mSeverity; 175 | }; 176 | 177 | //! \class Logger 178 | //! 179 | //! \brief Class which manages logging of TensorRT tools and samples 180 | //! 181 | //! \details This class provides a common interface for TensorRT tools and samples to log information to the console, 182 | //! and supports logging two types of messages: 183 | //! 184 | //! - Debugging messages with an associated severity (info, warning, error, or internal error/fatal) 185 | //! - Test pass/fail messages 186 | //! 187 | //! The advantage of having all samples use this class for logging as opposed to emitting directly to stdout/stderr is 188 | //! that the logic for controlling the verbosity and formatting of sample output is centralized in one location. 189 | //! 190 | //! In the future, this class could be extended to support dumping test results to a file in some standard format 191 | //! (for example, JUnit XML), and providing additional metadata (e.g. timing the duration of a test run). 192 | //! 193 | //! TODO: For backwards compatibility with existing samples, this class inherits directly from the nvinfer1::ILogger 194 | //! interface, which is problematic since there isn't a clean separation between messages coming from the TensorRT 195 | //! library and messages coming from the sample. 196 | //! 197 | //! In the future (once all samples are updated to use Logger::getTRTLogger() to access the ILogger) we can refactor the 198 | //! class to eliminate the inheritance and instead make the nvinfer1::ILogger implementation a member of the Logger 199 | //! object. 200 | 201 | class Logger : public nvinfer1::ILogger 202 | { 203 | public: 204 | Logger(Severity severity = Severity::kWARNING) 205 | : mReportableSeverity(severity) 206 | { 207 | } 208 | 209 | //! 210 | //! \enum TestResult 211 | //! \brief Represents the state of a given test 212 | //! 213 | enum class TestResult 214 | { 215 | kRUNNING, //!< The test is running 216 | kPASSED, //!< The test passed 217 | kFAILED, //!< The test failed 218 | kWAIVED //!< The test was waived 219 | }; 220 | 221 | //! 222 | //! \brief Forward-compatible method for retrieving the nvinfer::ILogger associated with this Logger 223 | //! \return The nvinfer1::ILogger associated with this Logger 224 | //! 225 | //! TODO Once all samples are updated to use this method to register the logger with TensorRT, 226 | //! we can eliminate the inheritance of Logger from ILogger 227 | //! 228 | nvinfer1::ILogger& getTRTLogger() 229 | { 230 | return *this; 231 | } 232 | 233 | //! 234 | //! \brief Implementation of the nvinfer1::ILogger::log() virtual method 235 | //! 236 | //! Note samples should not be calling this function directly; it will eventually go away once we eliminate the 237 | //! inheritance from nvinfer1::ILogger 238 | //! 239 | void log(Severity severity, const char* msg) override 240 | { 241 | LogStreamConsumer(mReportableSeverity, severity) << "[TRT] " << std::string(msg) << std::endl; 242 | } 243 | 244 | //! 245 | //! \brief Method for controlling the verbosity of logging output 246 | //! 247 | //! \param severity The logger will only emit messages that have severity of this level or higher. 248 | //! 249 | void setReportableSeverity(Severity severity) 250 | { 251 | mReportableSeverity = severity; 252 | } 253 | 254 | //! 255 | //! \brief Opaque handle that holds logging information for a particular test 256 | //! 257 | //! This object is an opaque handle to information used by the Logger to print test results. 258 | //! The sample must call Logger::defineTest() in order to obtain a TestAtom that can be used 259 | //! with Logger::reportTest{Start,End}(). 260 | //! 261 | class TestAtom 262 | { 263 | public: 264 | TestAtom(TestAtom&&) = default; 265 | 266 | private: 267 | friend class Logger; 268 | 269 | TestAtom(bool started, const std::string& name, const std::string& cmdline) 270 | : mStarted(started) 271 | , mName(name) 272 | , mCmdline(cmdline) 273 | { 274 | } 275 | 276 | bool mStarted; 277 | std::string mName; 278 | std::string mCmdline; 279 | }; 280 | 281 | //! 282 | //! \brief Define a test for logging 283 | //! 284 | //! \param[in] name The name of the test. This should be a string starting with 285 | //! "TensorRT" and containing dot-separated strings containing 286 | //! the characters [A-Za-z0-9_]. 287 | //! For example, "TensorRT.sample_googlenet" 288 | //! \param[in] cmdline The command line used to reproduce the test 289 | // 290 | //! \return a TestAtom that can be used in Logger::reportTest{Start,End}(). 291 | //! 292 | static TestAtom defineTest(const std::string& name, const std::string& cmdline) 293 | { 294 | return TestAtom(false, name, cmdline); 295 | } 296 | 297 | //! 298 | //! \brief A convenience overloaded version of defineTest() that accepts an array of command-line arguments 299 | //! as input 300 | //! 301 | //! \param[in] name The name of the test 302 | //! \param[in] argc The number of command-line arguments 303 | //! \param[in] argv The array of command-line arguments (given as C strings) 304 | //! 305 | //! \return a TestAtom that can be used in Logger::reportTest{Start,End}(). 306 | static TestAtom defineTest(const std::string& name, int argc, char const* const* argv) 307 | { 308 | auto cmdline = genCmdlineString(argc, argv); 309 | return defineTest(name, cmdline); 310 | } 311 | 312 | //! 313 | //! \brief Report that a test has started. 314 | //! 315 | //! \pre reportTestStart() has not been called yet for the given testAtom 316 | //! 317 | //! \param[in] testAtom The handle to the test that has started 318 | //! 319 | static void reportTestStart(TestAtom& testAtom) 320 | { 321 | reportTestResult(testAtom, TestResult::kRUNNING); 322 | assert(!testAtom.mStarted); 323 | testAtom.mStarted = true; 324 | } 325 | 326 | //! 327 | //! \brief Report that a test has ended. 328 | //! 329 | //! \pre reportTestStart() has been called for the given testAtom 330 | //! 331 | //! \param[in] testAtom The handle to the test that has ended 332 | //! \param[in] result The result of the test. Should be one of TestResult::kPASSED, 333 | //! TestResult::kFAILED, TestResult::kWAIVED 334 | //! 335 | static void reportTestEnd(const TestAtom& testAtom, TestResult result) 336 | { 337 | assert(result != TestResult::kRUNNING); 338 | assert(testAtom.mStarted); 339 | reportTestResult(testAtom, result); 340 | } 341 | 342 | static int reportPass(const TestAtom& testAtom) 343 | { 344 | reportTestEnd(testAtom, TestResult::kPASSED); 345 | return EXIT_SUCCESS; 346 | } 347 | 348 | static int reportFail(const TestAtom& testAtom) 349 | { 350 | reportTestEnd(testAtom, TestResult::kFAILED); 351 | return EXIT_FAILURE; 352 | } 353 | 354 | static int reportWaive(const TestAtom& testAtom) 355 | { 356 | reportTestEnd(testAtom, TestResult::kWAIVED); 357 | return EXIT_SUCCESS; 358 | } 359 | 360 | static int reportTest(const TestAtom& testAtom, bool pass) 361 | { 362 | return pass ? reportPass(testAtom) : reportFail(testAtom); 363 | } 364 | 365 | Severity getReportableSeverity() const 366 | { 367 | return mReportableSeverity; 368 | } 369 | 370 | private: 371 | //! 372 | //! \brief returns an appropriate string for prefixing a log message with the given severity 373 | //! 374 | static const char* severityPrefix(Severity severity) 375 | { 376 | switch (severity) 377 | { 378 | case Severity::kINTERNAL_ERROR: return "[F] "; 379 | case Severity::kERROR: return "[E] "; 380 | case Severity::kWARNING: return "[W] "; 381 | case Severity::kINFO: return "[I] "; 382 | case Severity::kVERBOSE: return "[V] "; 383 | default: assert(0); return ""; 384 | } 385 | } 386 | 387 | //! 388 | //! \brief returns an appropriate string for prefixing a test result message with the given result 389 | //! 390 | static const char* testResultString(TestResult result) 391 | { 392 | switch (result) 393 | { 394 | case TestResult::kRUNNING: return "RUNNING"; 395 | case TestResult::kPASSED: return "PASSED"; 396 | case TestResult::kFAILED: return "FAILED"; 397 | case TestResult::kWAIVED: return "WAIVED"; 398 | default: assert(0); return ""; 399 | } 400 | } 401 | 402 | //! 403 | //! \brief returns an appropriate output stream (cout or cerr) to use with the given severity 404 | //! 405 | static std::ostream& severityOstream(Severity severity) 406 | { 407 | return severity >= Severity::kINFO ? std::cout : std::cerr; 408 | } 409 | 410 | //! 411 | //! \brief method that implements logging test results 412 | //! 413 | static void reportTestResult(const TestAtom& testAtom, TestResult result) 414 | { 415 | severityOstream(Severity::kINFO) << "&&&& " << testResultString(result) << " " << testAtom.mName << " # " 416 | << testAtom.mCmdline << std::endl; 417 | } 418 | 419 | //! 420 | //! \brief generate a command line string from the given (argc, argv) values 421 | //! 422 | static std::string genCmdlineString(int argc, char const* const* argv) 423 | { 424 | std::stringstream ss; 425 | for (int i = 0; i < argc; i++) 426 | { 427 | if (i > 0) 428 | ss << " "; 429 | ss << argv[i]; 430 | } 431 | return ss.str(); 432 | } 433 | 434 | Severity mReportableSeverity; 435 | }; 436 | 437 | namespace 438 | { 439 | 440 | //! 441 | //! \brief produces a LogStreamConsumer object that can be used to log messages of severity kVERBOSE 442 | //! 443 | //! Example usage: 444 | //! 445 | //! LOG_VERBOSE(logger) << "hello world" << std::endl; 446 | //! 447 | inline LogStreamConsumer LOG_VERBOSE(const Logger& logger) 448 | { 449 | return LogStreamConsumer(logger.getReportableSeverity(), Severity::kVERBOSE); 450 | } 451 | 452 | //! 453 | //! \brief produces a LogStreamConsumer object that can be used to log messages of severity kINFO 454 | //! 455 | //! Example usage: 456 | //! 457 | //! LOG_INFO(logger) << "hello world" << std::endl; 458 | //! 459 | inline LogStreamConsumer LOG_INFO(const Logger& logger) 460 | { 461 | return LogStreamConsumer(logger.getReportableSeverity(), Severity::kINFO); 462 | } 463 | 464 | //! 465 | //! \brief produces a LogStreamConsumer object that can be used to log messages of severity kWARNING 466 | //! 467 | //! Example usage: 468 | //! 469 | //! LOG_WARN(logger) << "hello world" << std::endl; 470 | //! 471 | inline LogStreamConsumer LOG_WARN(const Logger& logger) 472 | { 473 | return LogStreamConsumer(logger.getReportableSeverity(), Severity::kWARNING); 474 | } 475 | 476 | //! 477 | //! \brief produces a LogStreamConsumer object that can be used to log messages of severity kERROR 478 | //! 479 | //! Example usage: 480 | //! 481 | //! LOG_ERROR(logger) << "hello world" << std::endl; 482 | //! 483 | inline LogStreamConsumer LOG_ERROR(const Logger& logger) 484 | { 485 | return LogStreamConsumer(logger.getReportableSeverity(), Severity::kERROR); 486 | } 487 | 488 | //! 489 | //! \brief produces a LogStreamConsumer object that can be used to log messages of severity kINTERNAL_ERROR 490 | // ("fatal" severity) 491 | //! 492 | //! Example usage: 493 | //! 494 | //! LOG_FATAL(logger) << "hello world" << std::endl; 495 | //! 496 | inline LogStreamConsumer LOG_FATAL(const Logger& logger) 497 | { 498 | return LogStreamConsumer(logger.getReportableSeverity(), Severity::kINTERNAL_ERROR); 499 | } 500 | 501 | } // anonymous namespace 502 | 503 | #endif // TENSORRT_LOGGING_H 504 | -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | #ifndef __TRT_UTILS_H_ 2 | #define __TRT_UTILS_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifndef CUDA_CHECK 10 | 11 | #define CUDA_CHECK(callstr) \ 12 | { \ 13 | cudaError_t error_code = callstr; \ 14 | if (error_code != cudaSuccess) { \ 15 | std::cerr << "CUDA error " << error_code << " at " << __FILE__ << ":" << __LINE__; \ 16 | assert(0); \ 17 | } \ 18 | } 19 | 20 | #endif 21 | 22 | namespace Tn 23 | { 24 | class Profiler : public nvinfer1::IProfiler 25 | { 26 | public: 27 | void printLayerTimes(int itrationsTimes) 28 | { 29 | float totalTime = 0; 30 | for (size_t i = 0; i < mProfile.size(); i++) 31 | { 32 | printf("%-40.40s %4.3fms\n", mProfile[i].first.c_str(), mProfile[i].second / itrationsTimes); 33 | totalTime += mProfile[i].second; 34 | } 35 | printf("Time over all layers: %4.3f\n", totalTime / itrationsTimes); 36 | } 37 | private: 38 | typedef std::pair Record; 39 | std::vector mProfile; 40 | 41 | virtual void reportLayerTime(const char* layerName, float ms) 42 | { 43 | auto record = std::find_if(mProfile.begin(), mProfile.end(), [&](const Record& r){ return r.first == layerName; }); 44 | if (record == mProfile.end()) 45 | mProfile.push_back(std::make_pair(layerName, ms)); 46 | else 47 | record->second += ms; 48 | } 49 | }; 50 | 51 | //Logger for TensorRT info/warning/errors 52 | class Logger : public nvinfer1::ILogger 53 | { 54 | public: 55 | 56 | Logger(): Logger(Severity::kWARNING) {} 57 | 58 | Logger(Severity severity): reportableSeverity(severity) {} 59 | 60 | void log(Severity severity, const char* msg) override 61 | { 62 | // suppress messages with severity enum value greater than the reportable 63 | if (severity > reportableSeverity) return; 64 | 65 | switch (severity) 66 | { 67 | case Severity::kINTERNAL_ERROR: std::cerr << "INTERNAL_ERROR: "; break; 68 | case Severity::kERROR: std::cerr << "ERROR: "; break; 69 | case Severity::kWARNING: std::cerr << "WARNING: "; break; 70 | case Severity::kINFO: std::cerr << "INFO: "; break; 71 | default: std::cerr << "UNKNOWN: "; break; 72 | } 73 | std::cerr << msg << std::endl; 74 | } 75 | 76 | Severity reportableSeverity{Severity::kWARNING}; 77 | }; 78 | 79 | template 80 | void write(char*& buffer, const T& val) 81 | { 82 | *reinterpret_cast(buffer) = val; 83 | buffer += sizeof(T); 84 | } 85 | 86 | template 87 | void read(const char*& buffer, T& val) 88 | { 89 | val = *reinterpret_cast(buffer); 90 | buffer += sizeof(T); 91 | } 92 | } 93 | 94 | #endif --------------------------------------------------------------------------------