diff --git a/runtime_data/openalpr.conf b/runtime_data/openalpr.conf index 3a39856..e352b6d 100644 --- a/runtime_data/openalpr.conf +++ b/runtime_data/openalpr.conf @@ -14,6 +14,8 @@ max_plate_height_percent = 100 detection_iteration_increase = 1.1 opencl_enabled = 0 +multithreading_cores = 1 + ocr_min_font_point = 6 diff --git a/src/misc_utilities/CMakeLists.txt b/src/misc_utilities/CMakeLists.txt index 3151749..f2786b9 100644 --- a/src/misc_utilities/CMakeLists.txt +++ b/src/misc_utilities/CMakeLists.txt @@ -4,33 +4,35 @@ target_link_libraries(openalpr) ADD_EXECUTABLE( sortstate sortstate.cpp ) -TARGET_LINK_LIBRARIES(sortstate +TARGET_LINK_LIBRARIES(sortstate openalpr support - ${OpenCV_LIBS} + ${OpenCV_LIBS} tesseract ) - + ADD_EXECUTABLE( classifychars classifychars.cpp ) -TARGET_LINK_LIBRARIES(classifychars +TARGET_LINK_LIBRARIES(classifychars openalpr support - ${OpenCV_LIBS} + ${OpenCV_LIBS} tesseract ) - - + + ADD_EXECUTABLE( benchmark benchmark.cpp ) -TARGET_LINK_LIBRARIES(benchmark +TARGET_LINK_LIBRARIES(benchmark openalpr support - ${OpenCV_LIBS} + ${OpenCV_LIBS} tesseract ) - - + + ADD_EXECUTABLE( prepcharsfortraining prepcharsfortraining.cpp ) -TARGET_LINK_LIBRARIES(prepcharsfortraining +TARGET_LINK_LIBRARIES(prepcharsfortraining support - ${OpenCV_LIBS} + ${OpenCV_LIBS} ) + + \ No newline at end of file diff --git a/src/openalpr/CMakeLists.txt b/src/openalpr/CMakeLists.txt index 9f01fab..2b5f3ea 100644 --- a/src/openalpr/CMakeLists.txt +++ b/src/openalpr/CMakeLists.txt @@ -23,9 +23,10 @@ set(lpr_source_files verticalhistogram.cpp trex.c cjson.c + tinythread/tinythread.cpp ) - + add_subdirectory(simpleini) add_subdirectory(support) @@ -35,4 +36,4 @@ add_library(openalpr ${lpr_source_files}) # Add definition for default runtime dir -add_definitions(-DDEFAULT_RUNTIME_DIR="${CMAKE_SOURCE_DIR}/../runtime_data/") +add_definitions(-DDEFAULT_RUNTIME_DIR="${CMAKE_SOURCE_DIR}/../runtime_data/") \ No newline at end of file diff --git a/src/openalpr/alpr_impl.cpp b/src/openalpr/alpr_impl.cpp index ecc3e8c..62927ed 100644 --- a/src/openalpr/alpr_impl.cpp +++ b/src/openalpr/alpr_impl.cpp @@ -1,55 +1,59 @@ /* * Copyright (c) 2013 New Designs Unlimited, LLC * Opensource Automated License Plate Recognition [http://www.openalpr.com] - * + * * This file is part of OpenAlpr. - * + * * OpenAlpr is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License - * version 3 as published by the Free Software Foundation - * + * it under the terms of the GNU Affero General Public License + * version 3 as published by the Free Software Foundation + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include "alpr_impl.h" +void plateAnalysisThread(void* arg); + AlprImpl::AlprImpl(const std::string country, const std::string runtimeDir) { config = new Config(country, runtimeDir); plateDetector = new RegionDetector(config); stateIdentifier = new StateIdentifier(config); ocr = new OCR(config); - + setNumThreads(0); + this->detectRegion = DEFAULT_DETECT_REGION; this->topN = DEFAULT_TOPN; this->defaultRegion = ""; - + if (config->opencl_enabled) { + cv::ocl::PlatformsInfo platinfo; cv::ocl::getOpenCLPlatforms(platinfo); - + for (int i = 0; i < platinfo.size(); i++) { - std::cout << platinfo[i]->platformName << std::endl; + std::cout << platinfo[i]->platformName << std::endl; } - + cv::ocl::DevicesInfo devices; cv::ocl::getOpenCLDevices(devices, cv::ocl::CVCL_DEVICE_TYPE_CPU); - + for (int i = 0; i < devices.size(); i++) std:: cout << devices[i]->deviceName << std::endl; - + if (devices.size() > 0) { cv::ocl::setDevice(devices[0]); - + cout << "Using OpenCL Device: " << devices[0]->deviceName << endl; } else @@ -58,7 +62,6 @@ AlprImpl::AlprImpl(const std::string country, const std::string runtimeDir) } } } - AlprImpl::~AlprImpl() { delete config; @@ -67,100 +70,43 @@ AlprImpl::~AlprImpl() delete ocr; } + std::vector AlprImpl::recognize(cv::Mat img) { timespec startTime; getTime(&startTime); + - vector response; + // Find all the candidate regions vector plateRegions = plateDetector->detect(img); - // Recognize. + // Get the number of threads specified and make sure the value is sane (cannot be greater than CPU cores or less than 1) + int numThreads = config->multithreading_cores; + if (numThreads > tthread::thread::hardware_concurrency()) + numThreads = tthread::thread::hardware_concurrency(); + if (numThreads <= 0) + numThreads = 1; - for (int i = 0; i < plateRegions.size(); i++) + + PlateDispatcher dispatcher(plateRegions, &img, + config, stateIdentifier, ocr, + topN, detectRegion, defaultRegion); + + // Spawn n threads to process all of the candidate regions and recognize + list threads; + for (int i = 0; i < numThreads; i++) { - timespec platestarttime; - getTime(&platestarttime); - - LicensePlateCandidate lp(img, plateRegions[i], config); - - lp.recognize(); - - if (lp.confidence > 10) - { - AlprResult plateResult; - plateResult.region = defaultRegion; - plateResult.regionConfidence = 0; - - for (int pointidx = 0; pointidx < 4; pointidx++) - { - plateResult.plate_points[pointidx].x = (int) lp.plateCorners[pointidx].x; - plateResult.plate_points[pointidx].y = (int) lp.plateCorners[pointidx].y; - } - - if (detectRegion) - { - char statecode[4]; - plateResult.regionConfidence = stateIdentifier->recognize(img, plateRegions[i], statecode); - if (plateResult.regionConfidence > 0) - { - plateResult.region = statecode; - } - } - - ocr->performOCR(lp.charSegmenter->getThresholds(), lp.charSegmenter->characters); - - ocr->postProcessor->analyze(plateResult.region, topN); - - //plateResult.characters = ocr->postProcessor->bestChars; - const vector ppResults = ocr->postProcessor->getResults(); - - int bestPlateIndex = 0; - - for (int pp = 0; pp < ppResults.size(); pp++) - { - if (pp >= topN) - break; - - // Set our "best plate" match to either the first entry, or the first entry with a postprocessor template match - if (bestPlateIndex == 0 && ppResults[pp].matchesTemplate) - bestPlateIndex = pp; - - if (ppResults[pp].letters.size() >= config->postProcessMinCharacters && - ppResults[pp].letters.size() <= config->postProcessMaxCharacters) - { - AlprPlate aplate; - aplate.characters = ppResults[pp].letters; - aplate.overall_confidence = ppResults[pp].totalscore; - aplate.matches_template = ppResults[pp].matchesTemplate; - plateResult.topNPlates.push_back(aplate); - } - } - plateResult.result_count = plateResult.topNPlates.size(); - - if (plateResult.topNPlates.size() > 0) - plateResult.bestPlate = plateResult.topNPlates[bestPlateIndex]; - - timespec plateEndTime; - getTime(&plateEndTime); - plateResult.processing_time_ms = diffclock(platestarttime, plateEndTime); - - if (plateResult.result_count > 0) - response.push_back(plateResult); - - if (config->debugGeneral) - { - rectangle(img, plateRegions[i], Scalar(0, 255, 0), 2); - for (int z = 0; z < 4; z++) - line(img, lp.plateCorners[z], lp.plateCorners[(z + 1) % 4], Scalar(255,0,255), 2); - } - } - else - { - if (config->debugGeneral) - rectangle(img, plateRegions[i], Scalar(0, 0, 255), 2); - } + tthread::thread * t = new tthread::thread(plateAnalysisThread, (void *) &dispatcher); + threads.push_back(t); + } + + // Wait for all threads to finish + for(list::iterator i = threads.begin(); i != threads.end(); ++ i) + { + tthread::thread* t = *i; + t->join(); + delete t; } if (config->debugTiming) @@ -169,7 +115,7 @@ std::vector AlprImpl::recognize(cv::Mat img) getTime(&endTime); cout << "Total Time to process image: " << diffclock(startTime, endTime) << "ms." << endl; } - + if (config->debugGeneral && config->debugShowImages) { displayImage(config, "Main Image", img); @@ -177,47 +123,175 @@ std::vector AlprImpl::recognize(cv::Mat img) while ((char) cv::waitKey(50) == -1) {} } - return response; + + // if (config->debugGeneral) +// { +// rectangle(img, plateRegion, Scalar(0, 255, 0), 2); +// for (int z = 0; z < 4; z++) +// line(img, lp.plateCorners[z], lp.plateCorners[(z + 1) % 4], Scalar(255,0,255), 2); +// } + +// if (config->debugGeneral) +// rectangle(img, plateRegion, Scalar(0, 0, 255), 2); + + return dispatcher.getRecognitionResults(); +} + +void plateAnalysisThread(void* arg) +{ + PlateDispatcher* dispatcher = (PlateDispatcher*) arg; + if (dispatcher->config->debugGeneral) + cout << "Thread: " << tthread::this_thread::get_id() << " Initialized" << endl; + + int loop_count = 0; + while (true) + { + + if (dispatcher->hasPlate() == false) + break; + + // Synchronized section + if (dispatcher->config->debugGeneral) + cout << "Thread: " << tthread::this_thread::get_id() << " loop " << ++loop_count << endl; + + // Get a single plate region from the queue + Rect plateRegion = dispatcher->nextPlate(); + + Mat img = dispatcher->getImageCopy(); + + + // Parallel section + timespec platestarttime; + getTime(&platestarttime); + + LicensePlateCandidate lp(img, plateRegion, dispatcher->config); + + lp.recognize(); + + + if (lp.confidence > 10) + { + AlprResult plateResult; + plateResult.region = dispatcher->defaultRegion; + plateResult.regionConfidence = 0; + + for (int pointidx = 0; pointidx < 4; pointidx++) + { + plateResult.plate_points[pointidx].x = (int) lp.plateCorners[pointidx].x; + plateResult.plate_points[pointidx].y = (int) lp.plateCorners[pointidx].y; + } + + if (dispatcher->detectRegion) + { + char statecode[4]; + plateResult.regionConfidence = dispatcher->stateIdentifier->recognize(img, plateRegion, statecode); + if (plateResult.regionConfidence > 0) + { + plateResult.region = statecode; + } + } + + + dispatcher->ocr->performOCR(lp.charSegmenter->getThresholds(), lp.charSegmenter->characters); + + dispatcher->ocr->postProcessor->analyze(plateResult.region, dispatcher->topN); + + //plateResult.characters = ocr->postProcessor->bestChars; + const vector ppResults = dispatcher->ocr->postProcessor->getResults(); + + int bestPlateIndex = 0; + + for (int pp = 0; pp < ppResults.size(); pp++) + { + if (pp >= dispatcher->topN) + break; + + // Set our "best plate" match to either the first entry, or the first entry with a postprocessor template match + if (bestPlateIndex == 0 && ppResults[pp].matchesTemplate) + bestPlateIndex = pp; + + if (ppResults[pp].letters.size() >= dispatcher->config->postProcessMinCharacters && + ppResults[pp].letters.size() <= dispatcher->config->postProcessMaxCharacters) + { + AlprPlate aplate; + aplate.characters = ppResults[pp].letters; + aplate.overall_confidence = ppResults[pp].totalscore; + aplate.matches_template = ppResults[pp].matchesTemplate; + plateResult.topNPlates.push_back(aplate); + } + } + plateResult.result_count = plateResult.topNPlates.size(); + + if (plateResult.topNPlates.size() > 0) + plateResult.bestPlate = plateResult.topNPlates[bestPlateIndex]; + + timespec plateEndTime; + getTime(&plateEndTime); + plateResult.processing_time_ms = diffclock(platestarttime, plateEndTime); + + if (plateResult.result_count > 0) + { + // Synchronized section + dispatcher->addResult(plateResult); + + } + + + } + + if (dispatcher->config->debugTiming) + { + timespec plateEndTime; + getTime(&plateEndTime); + cout << "Thread: " << tthread::this_thread::get_id() << " Finished loop " << loop_count << " in " << diffclock(platestarttime, plateEndTime) << "ms." << endl; + } + + } + + if (dispatcher->config->debugGeneral) + cout << "Thread: " << tthread::this_thread::get_id() << " Complete" << endl; } string AlprImpl::toJson(const vector< AlprResult > results) { - cJSON *root = cJSON_CreateArray(); - + cJSON *root = cJSON_CreateArray(); + for (int i = 0; i < results.size(); i++) { cJSON *resultObj = createJsonObj( &results[i] ); cJSON_AddItemToArray(root, resultObj); } - + // Print the JSON object to a string and return char *out; out=cJSON_PrintUnformatted(root); cJSON_Delete(root); - + string response(out); - + free(out); return response; } + + cJSON* AlprImpl::createJsonObj(const AlprResult* result) { cJSON *root, *coords, *candidates; - - root=cJSON_CreateObject(); - + + root=cJSON_CreateObject(); + cJSON_AddStringToObject(root,"plate", result->bestPlate.characters.c_str()); cJSON_AddNumberToObject(root,"confidence", result->bestPlate.overall_confidence); cJSON_AddNumberToObject(root,"matches_template", result->bestPlate.matches_template); - + cJSON_AddStringToObject(root,"region", result->region.c_str()); cJSON_AddNumberToObject(root,"region_confidence", result->regionConfidence); - + cJSON_AddNumberToObject(root,"processing_time_ms", result->processing_time_ms); - + cJSON_AddItemToObject(root, "coordinates", coords=cJSON_CreateArray()); - for (int i=0; i<4; i++) + for (int i=0;i<4;i++) { cJSON *coords_object; coords_object = cJSON_CreateObject(); @@ -226,7 +300,8 @@ cJSON* AlprImpl::createJsonObj(const AlprResult* result) cJSON_AddItemToArray(coords, coords_object); } - + + cJSON_AddItemToObject(root, "candidates", candidates=cJSON_CreateArray()); for (int i = 0; i < result->topNPlates.size(); i++) { @@ -238,21 +313,21 @@ cJSON* AlprImpl::createJsonObj(const AlprResult* result) cJSON_AddItemToArray(candidates, candidate_object); } - + return root; } + void AlprImpl::setDetectRegion(bool detectRegion) { this->detectRegion = detectRegion; } - void AlprImpl::setTopN(int topn) { this->topN = topn; } - void AlprImpl::setDefaultRegion(string region) { this->defaultRegion = region; } + diff --git a/src/openalpr/alpr_impl.h b/src/openalpr/alpr_impl.h index 41bc61c..1dadd25 100644 --- a/src/openalpr/alpr_impl.h +++ b/src/openalpr/alpr_impl.h @@ -1,25 +1,28 @@ /* * Copyright (c) 2013 New Designs Unlimited, LLC * Opensource Automated License Plate Recognition [http://www.openalpr.com] - * + * * This file is part of OpenAlpr. - * + * * OpenAlpr is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License - * version 3 as published by the Free Software Foundation - * + * it under the terms of the GNU Affero General Public License + * version 3 as published by the Free Software Foundation + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + #ifndef ALPRIMPL_H #define ALPRIMPL_H +#include + #include "alpr.h" #include "config.h" @@ -33,6 +36,9 @@ #include #include "opencv2/ocl/ocl.hpp" + + +#include "tinythread/tinythread.h" #define DEFAULT_TOPN 25 #define DEFAULT_DETECT_REGION false @@ -45,28 +51,105 @@ class AlprImpl virtual ~AlprImpl(); std::vector recognize(cv::Mat img); - + void applyRegionTemplate(AlprResult* result, std::string region); - + void setDetectRegion(bool detectRegion); void setTopN(int topn); void setDefaultRegion(string region); - + std::string toJson(const vector results); - + Config* config; - + private: - + RegionDetector* plateDetector; StateIdentifier* stateIdentifier; OCR* ocr; - + int topN; bool detectRegion; std::string defaultRegion; - + cJSON* createJsonObj(const AlprResult* result); }; -#endif // ALPRIMPL_H +class PlateDispatcher +{ + public: + PlateDispatcher(vector plateRegions, cv::Mat* image, + Config* config, + StateIdentifier* stateIdentifier, + OCR* ocr, + int topN, bool detectRegion, std::string defaultRegion) + { + this->plateRegions = plateRegions; + this->frame = image; + + this->config = config; + this->stateIdentifier = stateIdentifier; + this->ocr = ocr; + this->topN = topN; + this->detectRegion = detectRegion; + this->defaultRegion = defaultRegion; + } + + cv::Mat getImageCopy() + { + tthread::lock_guard guard(mMutex); + + Mat img(this->frame->size(), this->frame->type()); + this->frame->copyTo(img); + + return img; + } + + bool hasPlate() + { + bool plateAvailable; + mMutex.lock(); + plateAvailable = plateRegions.size() > 0; + mMutex.unlock(); + return plateAvailable; + } + Rect nextPlate() + { + tthread::lock_guard guard(mMutex); + + Rect plateRegion = plateRegions[plateRegions.size() - 1]; + plateRegions.pop_back(); + + return plateRegion; + } + + void addResult(AlprResult recognitionResult) + { + tthread::lock_guard guard(mMutex); + recognitionResults.push_back(recognitionResult); + } + + vector getRecognitionResults() + { + return recognitionResults; + } + + + StateIdentifier* stateIdentifier; + OCR* ocr; + Config* config; + + int topN; + bool detectRegion; + std::string defaultRegion; + + private: + + tthread::mutex mMutex; + cv::Mat* frame; + vector plateRegions; + vector recognitionResults; + +}; + +#endif // ALPRIMPL_H \ No newline at end of file diff --git a/src/openalpr/config.cpp b/src/openalpr/config.cpp index cc9c24f..f65965b 100644 --- a/src/openalpr/config.cpp +++ b/src/openalpr/config.cpp @@ -1,35 +1,37 @@ /* * Copyright (c) 2013 New Designs Unlimited, LLC * Opensource Automated License Plate Recognition [http://www.openalpr.com] - * + * * This file is part of OpenAlpr. - * + * * OpenAlpr is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License - * version 3 as published by the Free Software Foundation - * + * it under the terms of the GNU Affero General Public License + * version 3 as published by the Free Software Foundation + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include "config.h" + Config::Config(const std::string country, const std::string runtimeBaseDir) { this->runtimeBaseDir = runtimeBaseDir; - + ini = new CSimpleIniA(); - + char* envRuntimeDir; envRuntimeDir = getenv (ENV_VARIABLE_RUNTIME_DIR); if (runtimeBaseDir.compare("") != 0) { - // User has supplied a runtime directory. Use that. + // User has supplied a runtime directory. Use that. + } else if (envRuntimeDir!=NULL) { @@ -41,9 +43,9 @@ Config::Config(const std::string country, const std::string runtimeBaseDir) // Use the default this->runtimeBaseDir = DEFAULT_RUNTIME_DIR; } - + string configFile = (this->runtimeBaseDir + CONFIG_FILE); - + if (DirectoryExists(this->runtimeBaseDir.c_str()) == false) { std::cerr << "--(!)Runtime directory '" << this->runtimeBaseDir << "' does not exist!" << endl; @@ -54,14 +56,13 @@ Config::Config(const std::string country, const std::string runtimeBaseDir) std::cerr << "--(!)Runtime directory '" << this->runtimeBaseDir << "' does not contain a config file '" << CONFIG_FILE << "'!" << endl; return; } - + ini->LoadFile(configFile.c_str()); - + this->country = country; - + loadValues(country); } - Config::~Config() { delete ini; @@ -69,56 +70,59 @@ Config::~Config() void Config::loadValues(string country) { + opencl_enabled = getBoolean("common", "opencl_enabled", false); - + multithreading_cores = getInt("common", "multithreading_cores", 1); + detection_iteration_increase = getFloat("common", "detection_iteration_increase", 1.1); - maxPlateWidthPercent = getFloat("common", "max_plate_width_percent", 100); maxPlateHeightPercent = getFloat("common", "max_plate_height_percent", 100); minPlateSizeWidthPx = getInt(country, "min_plate_size_width_px", 100); minPlateSizeHeightPx = getInt(country, "min_plate_size_height_px", 100); - + plateWidthMM = getFloat(country, "plate_width_mm", 100); plateHeightMM = getFloat(country, "plate_height_mm", 100); - + charHeightMM = getFloat(country, "char_height_mm", 100); charWidthMM = getFloat(country, "char_width_mm", 100); charWhitespaceTopMM = getFloat(country, "char_whitespace_top_mm", 100); charWhitespaceBotMM = getFloat(country, "char_whitespace_bot_mm", 100); - + templateWidthPx = getInt(country, "template_max_width_px", 100); templateHeightPx = getInt(country, "template_max_height_px", 100); - + float ocrImagePercent = getFloat("common", "ocr_img_size_percent", 100); ocrImageWidthPx = round(((float) templateWidthPx) * ocrImagePercent); ocrImageHeightPx = round(((float)templateHeightPx) * ocrImagePercent); + float stateIdImagePercent = getFloat("common", "state_id_img_size_percent", 100); stateIdImageWidthPx = round(((float)templateWidthPx) * ocrImagePercent); stateIdimageHeightPx = round(((float)templateHeightPx) * ocrImagePercent); + charAnalysisMinPercent = getFloat(country, "char_analysis_min_pct", 0); charAnalysisHeightRange = getFloat(country, "char_analysis_height_range", 0); charAnalysisHeightStepSize = getFloat(country, "char_analysis_height_step_size", 0); charAnalysisNumSteps = getInt(country, "char_analysis_height_num_steps", 0); - + segmentationMinBoxWidthPx = getInt(country, "segmentation_min_box_width_px", 0); segmentationMinCharHeightPercent = getFloat(country, "segmentation_min_charheight_percent", 0); segmentationMaxCharWidthvsAverage = getFloat(country, "segmentation_max_segment_width_percent_vs_average", 0); - + plateLinesSensitivityVertical = getFloat(country, "plateline_sensitivity_vertical", 0); plateLinesSensitivityHorizontal = getFloat(country, "plateline_sensitivity_horizontal", 0); - + ocrLanguage = getString(country, "ocr_language", "none"); ocrMinFontSize = getInt("common", "ocr_min_font_point", 100); - + postProcessMinConfidence = getFloat("common", "postprocess_min_confidence", 100); postProcessConfidenceSkipLevel = getFloat("common", "postprocess_confidence_skip_level", 100); postProcessMaxSubstitutions = getInt("common", "postprocess_max_substitutions", 100); - postProcessMinCharacters = getInt("common", "postprocess_min_characters", 100); - postProcessMaxCharacters = getInt("common", "postprocess_max_characters", 100); - + postProcessMinCharacters = getInt("common", "postprocess_min_characers", 100); + postProcessMaxCharacters = getInt("common", "postprocess_max_characers", 100); + debugGeneral = getBoolean("debug", "general", false); debugTiming = getBoolean("debug", "timing", false); debugStateId = getBoolean("debug", "state_id", false); @@ -131,6 +135,7 @@ void Config::loadValues(string country) debugOcr = getBoolean("debug", "ocr", false); debugPostProcess = getBoolean("debug", "postprocess", false); debugShowImages = getBoolean("debug", "show_images", false); + } void Config::debugOff() @@ -148,26 +153,27 @@ void Config::debugOff() debugPostProcess = false; } + string Config::getCascadeRuntimeDir() { return this->runtimeBaseDir + CASCADE_DIR; } - string Config::getKeypointsRuntimeDir() { return this->runtimeBaseDir + KEYPOINTS_DIR; } - string Config::getPostProcessRuntimeDir() { return this->runtimeBaseDir + POSTPROCESS_DIR; } - string Config::getTessdataPrefix() { return "TESSDATA_PREFIX=" + this->runtimeBaseDir + "/ocr/"; } + + + float Config::getFloat(string section, string key, float defaultValue) { const char * pszValue = ini->GetValue(section.c_str(), key.c_str(), NULL /*default*/); @@ -176,11 +182,10 @@ float Config::getFloat(string section, string key, float defaultValue) std::cout << "Error: missing configuration entry for: " << section << "->" << key << endl; return defaultValue; } - + float val = atof(pszValue); return val; } - int Config::getInt(string section, string key, int defaultValue) { const char * pszValue = ini->GetValue(section.c_str(), key.c_str(), NULL /*default*/); @@ -189,11 +194,10 @@ int Config::getInt(string section, string key, int defaultValue) std::cout << "Error: missing configuration entry for: " << section << "->" << key << endl; return defaultValue; } - + int val = atoi(pszValue); return val; } - bool Config::getBoolean(string section, string key, bool defaultValue) { const char * pszValue = ini->GetValue(section.c_str(), key.c_str(), NULL /*default*/); @@ -202,11 +206,10 @@ bool Config::getBoolean(string section, string key, bool defaultValue) std::cout << "Error: missing configuration entry for: " << section << "->" << key << endl; return defaultValue; } - + int val = atoi(pszValue); return val != 0; } - string Config::getString(string section, string key, string defaultValue) { const char * pszValue = ini->GetValue(section.c_str(), key.c_str(), NULL /*default*/); @@ -215,7 +218,7 @@ string Config::getString(string section, string key, string defaultValue) std::cout << "Error: missing configuration entry for: " << section << "->" << key << endl; return defaultValue; } - + string val = string(pszValue); return val; } diff --git a/src/openalpr/config.h b/src/openalpr/config.h index e2e9baf..27614a2 100644 --- a/src/openalpr/config.h +++ b/src/openalpr/config.h @@ -1,25 +1,27 @@ /* * Copyright (c) 2013 New Designs Unlimited, LLC * Opensource Automated License Plate Recognition [http://www.openalpr.com] - * + * * This file is part of OpenAlpr. - * + * * OpenAlpr is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License - * version 3 as published by the Free Software Foundation - * + * it under the terms of the GNU Affero General Public License + * version 3 as published by the Free Software Foundation + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + #ifndef CONFIG_H #define CONFIG_H + #include "simpleini/simpleini.h" #include "support/filesystem.h" @@ -40,55 +42,56 @@ class Config virtual ~Config(); string country; - - bool opencl_enabled; - - float detection_iteration_increase; + bool opencl_enabled; + int multithreading_cores; + + float detection_iteration_increase; float maxPlateWidthPercent; float maxPlateHeightPercent; - + float minPlateSizeWidthPx; float minPlateSizeHeightPx; - + float plateWidthMM; float plateHeightMM; - + float charHeightMM; float charWidthMM; float charWhitespaceTopMM; float charWhitespaceBotMM; - + int templateWidthPx; int templateHeightPx; - + int ocrImageWidthPx; int ocrImageHeightPx; - + int stateIdImageWidthPx; int stateIdimageHeightPx; - + float charAnalysisMinPercent; float charAnalysisHeightRange; float charAnalysisHeightStepSize; int charAnalysisNumSteps; - + float plateLinesSensitivityVertical; float plateLinesSensitivityHorizontal; int segmentationMinBoxWidthPx; float segmentationMinCharHeightPercent; float segmentationMaxCharWidthvsAverage; - + string ocrLanguage; int ocrMinFontSize; - + float postProcessMinConfidence; float postProcessConfidenceSkipLevel; int postProcessMaxSubstitutions; int postProcessMinCharacters; int postProcessMaxCharacters; + bool debugGeneral; bool debugTiming; bool debugStateId; @@ -101,25 +104,26 @@ class Config bool debugOcr; bool debugPostProcess; bool debugShowImages; - + void debugOff(); - + string getKeypointsRuntimeDir(); string getCascadeRuntimeDir(); string getPostProcessRuntimeDir(); string getTessdataPrefix(); - private: +private: CSimpleIniA* ini; string runtimeBaseDir; - + void loadValues(string country); - + int getInt(string section, string key, int defaultValue); float getFloat(string section, string key, float defaultValue); string getString(string section, string key, string defaultValue); bool getBoolean(string section, string key, bool defaultValue); }; -#endif // CONFIG_H + +#endif // CONFIG_H \ No newline at end of file diff --git a/src/openalpr/tinythread/fast_mutex.h b/src/openalpr/tinythread/fast_mutex.h new file mode 100644 index 0000000..4d4b7cc --- /dev/null +++ b/src/openalpr/tinythread/fast_mutex.h @@ -0,0 +1,248 @@ +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef _FAST_MUTEX_H_ +#define _FAST_MUTEX_H_ + +/// @file + +// Which platform are we on? +#if !defined(_TTHREAD_PLATFORM_DEFINED_) + #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) + #define _TTHREAD_WIN32_ + #else + #define _TTHREAD_POSIX_ + #endif + #define _TTHREAD_PLATFORM_DEFINED_ +#endif + +// Check if we can support the assembly language level implementation (otherwise +// revert to the system API) +#if (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))) || \ + (defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))) || \ + (defined(__GNUC__) && (defined(__ppc__))) + #define _FAST_MUTEX_ASM_ +#else + #define _FAST_MUTEX_SYS_ +#endif + +#if defined(_TTHREAD_WIN32_) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #define __UNDEF_LEAN_AND_MEAN + #endif + #include + #ifdef __UNDEF_LEAN_AND_MEAN + #undef WIN32_LEAN_AND_MEAN + #undef __UNDEF_LEAN_AND_MEAN + #endif +#else + #ifdef _FAST_MUTEX_ASM_ + #include + #else + #include + #endif +#endif + +namespace tthread { + +/// Fast mutex class. +/// This is a mutual exclusion object for synchronizing access to shared +/// memory areas for several threads. It is similar to the tthread::mutex class, +/// but instead of using system level functions, it is implemented as an atomic +/// spin lock with very low CPU overhead. +/// +/// The \c fast_mutex class is NOT compatible with the \c condition_variable +/// class (however, it IS compatible with the \c lock_guard class). It should +/// also be noted that the \c fast_mutex class typically does not provide +/// as accurate thread scheduling as a the standard \c mutex class does. +/// +/// Because of the limitations of the class, it should only be used in +/// situations where the mutex needs to be locked/unlocked very frequently. +/// +/// @note The "fast" version of this class relies on inline assembler language, +/// which is currently only supported for 32/64-bit Intel x86/AMD64 and +/// PowerPC architectures on a limited number of compilers (GNU g++ and MS +/// Visual C++). +/// For other architectures/compilers, system functions are used instead. +class fast_mutex { + public: + /// Constructor. +#if defined(_FAST_MUTEX_ASM_) + fast_mutex() : mLock(0) {} +#else + fast_mutex() + { + #if defined(_TTHREAD_WIN32_) + InitializeCriticalSection(&mHandle); + #elif defined(_TTHREAD_POSIX_) + pthread_mutex_init(&mHandle, NULL); + #endif + } +#endif + +#if !defined(_FAST_MUTEX_ASM_) + /// Destructor. + ~fast_mutex() + { + #if defined(_TTHREAD_WIN32_) + DeleteCriticalSection(&mHandle); + #elif defined(_TTHREAD_POSIX_) + pthread_mutex_destroy(&mHandle); + #endif + } +#endif + + /// Lock the mutex. + /// The method will block the calling thread until a lock on the mutex can + /// be obtained. The mutex remains locked until \c unlock() is called. + /// @see lock_guard + inline void lock() + { +#if defined(_FAST_MUTEX_ASM_) + bool gotLock; + do { + gotLock = try_lock(); + if(!gotLock) + { + #if defined(_TTHREAD_WIN32_) + Sleep(0); + #elif defined(_TTHREAD_POSIX_) + sched_yield(); + #endif + } + } while(!gotLock); +#else + #if defined(_TTHREAD_WIN32_) + EnterCriticalSection(&mHandle); + #elif defined(_TTHREAD_POSIX_) + pthread_mutex_lock(&mHandle); + #endif +#endif + } + + /// Try to lock the mutex. + /// The method will try to lock the mutex. If it fails, the function will + /// return immediately (non-blocking). + /// @return \c true if the lock was acquired, or \c false if the lock could + /// not be acquired. + inline bool try_lock() + { +#if defined(_FAST_MUTEX_ASM_) + int oldLock; + #if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + asm volatile ( + "movl $1,%%eax\n\t" + "xchg %%eax,%0\n\t" + "movl %%eax,%1\n\t" + : "=m" (mLock), "=m" (oldLock) + : + : "%eax", "memory" + ); + #elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) + int *ptrLock = &mLock; + __asm { + mov eax,1 + mov ecx,ptrLock + xchg eax,[ecx] + mov oldLock,eax + } + #elif defined(__GNUC__) && (defined(__ppc__)) + int newLock = 1; + asm volatile ( + "\n1:\n\t" + "lwarx %0,0,%1\n\t" + "cmpwi 0,%0,0\n\t" + "bne- 2f\n\t" + "stwcx. %2,0,%1\n\t" + "bne- 1b\n\t" + "isync\n" + "2:\n\t" + : "=&r" (oldLock) + : "r" (&mLock), "r" (newLock) + : "cr0", "memory" + ); + #endif + return (oldLock == 0); +#else + #if defined(_TTHREAD_WIN32_) + return TryEnterCriticalSection(&mHandle) ? true : false; + #elif defined(_TTHREAD_POSIX_) + return (pthread_mutex_trylock(&mHandle) == 0) ? true : false; + #endif +#endif + } + + /// Unlock the mutex. + /// If any threads are waiting for the lock on this mutex, one of them will + /// be unblocked. + inline void unlock() + { +#if defined(_FAST_MUTEX_ASM_) + #if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + asm volatile ( + "movl $0,%%eax\n\t" + "xchg %%eax,%0\n\t" + : "=m" (mLock) + : + : "%eax", "memory" + ); + #elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) + int *ptrLock = &mLock; + __asm { + mov eax,0 + mov ecx,ptrLock + xchg eax,[ecx] + } + #elif defined(__GNUC__) && (defined(__ppc__)) + asm volatile ( + "sync\n\t" // Replace with lwsync where possible? + : : : "memory" + ); + mLock = 0; + #endif +#else + #if defined(_TTHREAD_WIN32_) + LeaveCriticalSection(&mHandle); + #elif defined(_TTHREAD_POSIX_) + pthread_mutex_unlock(&mHandle); + #endif +#endif + } + + private: +#if defined(_FAST_MUTEX_ASM_) + int mLock; +#else + #if defined(_TTHREAD_WIN32_) + CRITICAL_SECTION mHandle; + #elif defined(_TTHREAD_POSIX_) + pthread_mutex_t mHandle; + #endif +#endif +}; + +} + +#endif // _FAST_MUTEX_H_ + diff --git a/src/openalpr/tinythread/tinythread.cpp b/src/openalpr/tinythread/tinythread.cpp new file mode 100644 index 0000000..690ecee --- /dev/null +++ b/src/openalpr/tinythread/tinythread.cpp @@ -0,0 +1,303 @@ +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include "tinythread.h" + +#if defined(_TTHREAD_POSIX_) + #include + #include +#elif defined(_TTHREAD_WIN32_) + #include +#endif + + +namespace tthread { + +//------------------------------------------------------------------------------ +// condition_variable +//------------------------------------------------------------------------------ +// NOTE 1: The Win32 implementation of the condition_variable class is based on +// the corresponding implementation in GLFW, which in turn is based on a +// description by Douglas C. Schmidt and Irfan Pyarali: +// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html +// +// NOTE 2: Windows Vista actually has native support for condition variables +// (InitializeConditionVariable, WakeConditionVariable, etc), but we want to +// be portable with pre-Vista Windows versions, so TinyThread++ does not use +// Vista condition variables. +//------------------------------------------------------------------------------ + +#if defined(_TTHREAD_WIN32_) + #define _CONDITION_EVENT_ONE 0 + #define _CONDITION_EVENT_ALL 1 +#endif + +#if defined(_TTHREAD_WIN32_) +condition_variable::condition_variable() : mWaitersCount(0) +{ + mEvents[_CONDITION_EVENT_ONE] = CreateEvent(NULL, FALSE, FALSE, NULL); + mEvents[_CONDITION_EVENT_ALL] = CreateEvent(NULL, TRUE, FALSE, NULL); + InitializeCriticalSection(&mWaitersCountLock); +} +#endif + +#if defined(_TTHREAD_WIN32_) +condition_variable::~condition_variable() +{ + CloseHandle(mEvents[_CONDITION_EVENT_ONE]); + CloseHandle(mEvents[_CONDITION_EVENT_ALL]); + DeleteCriticalSection(&mWaitersCountLock); +} +#endif + +#if defined(_TTHREAD_WIN32_) +void condition_variable::_wait() +{ + // Wait for either event to become signaled due to notify_one() or + // notify_all() being called + int result = WaitForMultipleObjects(2, mEvents, FALSE, INFINITE); + + // Check if we are the last waiter + EnterCriticalSection(&mWaitersCountLock); + -- mWaitersCount; + bool lastWaiter = (result == (WAIT_OBJECT_0 + _CONDITION_EVENT_ALL)) && + (mWaitersCount == 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we are the last waiter to be notified to stop waiting, reset the event + if(lastWaiter) + ResetEvent(mEvents[_CONDITION_EVENT_ALL]); +} +#endif + +#if defined(_TTHREAD_WIN32_) +void condition_variable::notify_one() +{ + // Are there any waiters? + EnterCriticalSection(&mWaitersCountLock); + bool haveWaiters = (mWaitersCount > 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we have any waiting threads, send them a signal + if(haveWaiters) + SetEvent(mEvents[_CONDITION_EVENT_ONE]); +} +#endif + +#if defined(_TTHREAD_WIN32_) +void condition_variable::notify_all() +{ + // Are there any waiters? + EnterCriticalSection(&mWaitersCountLock); + bool haveWaiters = (mWaitersCount > 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we have any waiting threads, send them a signal + if(haveWaiters) + SetEvent(mEvents[_CONDITION_EVENT_ALL]); +} +#endif + + +//------------------------------------------------------------------------------ +// POSIX pthread_t to unique thread::id mapping logic. +// Note: Here we use a global thread safe std::map to convert instances of +// pthread_t to small thread identifier numbers (unique within one process). +// This method should be portable across different POSIX implementations. +//------------------------------------------------------------------------------ + +#if defined(_TTHREAD_POSIX_) +static thread::id _pthread_t_to_ID(const pthread_t &aHandle) +{ + static mutex idMapLock; + static std::map idMap; + static unsigned long int idCount(1); + + lock_guard guard(idMapLock); + if(idMap.find(aHandle) == idMap.end()) + idMap[aHandle] = idCount ++; + return thread::id(idMap[aHandle]); +} +#endif // _TTHREAD_POSIX_ + + +//------------------------------------------------------------------------------ +// thread +//------------------------------------------------------------------------------ + +/// Information to pass to the new thread (what to run). +struct _thread_start_info { + void (*mFunction)(void *); ///< Pointer to the function to be executed. + void * mArg; ///< Function argument for the thread function. + thread * mThread; ///< Pointer to the thread object. +}; + +// Thread wrapper function. +#if defined(_TTHREAD_WIN32_) +unsigned WINAPI thread::wrapper_function(void * aArg) +#elif defined(_TTHREAD_POSIX_) +void * thread::wrapper_function(void * aArg) +#endif +{ + // Get thread startup information + _thread_start_info * ti = (_thread_start_info *) aArg; + + try + { + // Call the actual client thread function + ti->mFunction(ti->mArg); + } + catch(...) + { + // Uncaught exceptions will terminate the application (default behavior + // according to C++11) + std::terminate(); + } + + // The thread is no longer executing + lock_guard guard(ti->mThread->mDataMutex); + ti->mThread->mNotAThread = true; + + // The thread is responsible for freeing the startup information + delete ti; + + return 0; +} + +thread::thread(void (*aFunction)(void *), void * aArg) +{ + // Serialize access to this thread structure + lock_guard guard(mDataMutex); + + // Fill out the thread startup information (passed to the thread wrapper, + // which will eventually free it) + _thread_start_info * ti = new _thread_start_info; + ti->mFunction = aFunction; + ti->mArg = aArg; + ti->mThread = this; + + // The thread is now alive + mNotAThread = false; + + // Create the thread +#if defined(_TTHREAD_WIN32_) + mHandle = (HANDLE) _beginthreadex(0, 0, wrapper_function, (void *) ti, 0, &mWin32ThreadID); +#elif defined(_TTHREAD_POSIX_) + if(pthread_create(&mHandle, NULL, wrapper_function, (void *) ti) != 0) + mHandle = 0; +#endif + + // Did we fail to create the thread? + if(!mHandle) + { + mNotAThread = true; + delete ti; + } +} + +thread::~thread() +{ + if(joinable()) + std::terminate(); +} + +void thread::join() +{ + if(joinable()) + { +#if defined(_TTHREAD_WIN32_) + WaitForSingleObject(mHandle, INFINITE); + CloseHandle(mHandle); +#elif defined(_TTHREAD_POSIX_) + pthread_join(mHandle, NULL); +#endif + } +} + +bool thread::joinable() const +{ + mDataMutex.lock(); + bool result = !mNotAThread; + mDataMutex.unlock(); + return result; +} + +void thread::detach() +{ + mDataMutex.lock(); + if(!mNotAThread) + { +#if defined(_TTHREAD_WIN32_) + CloseHandle(mHandle); +#elif defined(_TTHREAD_POSIX_) + pthread_detach(mHandle); +#endif + mNotAThread = true; + } + mDataMutex.unlock(); +} + +thread::id thread::get_id() const +{ + if(!joinable()) + return id(); +#if defined(_TTHREAD_WIN32_) + return id((unsigned long int) mWin32ThreadID); +#elif defined(_TTHREAD_POSIX_) + return _pthread_t_to_ID(mHandle); +#endif +} + +unsigned thread::hardware_concurrency() +{ +#if defined(_TTHREAD_WIN32_) + SYSTEM_INFO si; + GetSystemInfo(&si); + return (int) si.dwNumberOfProcessors; +#elif defined(_SC_NPROCESSORS_ONLN) + return (int) sysconf(_SC_NPROCESSORS_ONLN); +#elif defined(_SC_NPROC_ONLN) + return (int) sysconf(_SC_NPROC_ONLN); +#else + // The standard requires this function to return zero if the number of + // hardware cores could not be determined. + return 0; +#endif +} + + +//------------------------------------------------------------------------------ +// this_thread +//------------------------------------------------------------------------------ + +thread::id this_thread::get_id() +{ +#if defined(_TTHREAD_WIN32_) + return thread::id((unsigned long int) GetCurrentThreadId()); +#elif defined(_TTHREAD_POSIX_) + return _pthread_t_to_ID(pthread_self()); +#endif +} + +} diff --git a/src/openalpr/tinythread/tinythread.h b/src/openalpr/tinythread/tinythread.h new file mode 100644 index 0000000..aed7b58 --- /dev/null +++ b/src/openalpr/tinythread/tinythread.h @@ -0,0 +1,714 @@ +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef _TINYTHREAD_H_ +#define _TINYTHREAD_H_ + +/// @file +/// @mainpage TinyThread++ API Reference +/// +/// @section intro_sec Introduction +/// TinyThread++ is a minimal, portable implementation of basic threading +/// classes for C++. +/// +/// They closely mimic the functionality and naming of the C++11 standard, and +/// should be easily replaceable with the corresponding std:: variants. +/// +/// @section port_sec Portability +/// The Win32 variant uses the native Win32 API for implementing the thread +/// classes, while for other systems, the POSIX threads API (pthread) is used. +/// +/// @section class_sec Classes +/// In order to mimic the threading API of the C++11 standard, subsets of +/// several classes are provided. The fundamental classes are: +/// @li tthread::thread +/// @li tthread::mutex +/// @li tthread::recursive_mutex +/// @li tthread::condition_variable +/// @li tthread::lock_guard +/// @li tthread::fast_mutex +/// +/// @section misc_sec Miscellaneous +/// The following special keywords are available: #thread_local. +/// +/// For more detailed information (including additional classes), browse the +/// different sections of this documentation. A good place to start is: +/// tinythread.h. + +// Which platform are we on? +#if !defined(_TTHREAD_PLATFORM_DEFINED_) + #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) + #define _TTHREAD_WIN32_ + #else + #define _TTHREAD_POSIX_ + #endif + #define _TTHREAD_PLATFORM_DEFINED_ +#endif + +// Platform specific includes +#if defined(_TTHREAD_WIN32_) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #define __UNDEF_LEAN_AND_MEAN + #endif + #include + #ifdef __UNDEF_LEAN_AND_MEAN + #undef WIN32_LEAN_AND_MEAN + #undef __UNDEF_LEAN_AND_MEAN + #endif +#else + #include + #include + #include + #include +#endif + +// Generic includes +#include + +/// TinyThread++ version (major number). +#define TINYTHREAD_VERSION_MAJOR 1 +/// TinyThread++ version (minor number). +#define TINYTHREAD_VERSION_MINOR 1 +/// TinyThread++ version (full version). +#define TINYTHREAD_VERSION (TINYTHREAD_VERSION_MAJOR * 100 + TINYTHREAD_VERSION_MINOR) + +// Do we have a fully featured C++11 compiler? +#if (__cplusplus > 199711L) || (defined(__STDCXX_VERSION__) && (__STDCXX_VERSION__ >= 201001L)) + #define _TTHREAD_CPP11_ +#endif + +// ...at least partial C++11? +#if defined(_TTHREAD_CPP11_) || defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__GXX_EXPERIMENTAL_CPP0X__) + #define _TTHREAD_CPP11_PARTIAL_ +#endif + +// Macro for disabling assignments of objects. +#ifdef _TTHREAD_CPP11_PARTIAL_ + #define _TTHREAD_DISABLE_ASSIGNMENT(name) \ + name(const name&) = delete; \ + name& operator=(const name&) = delete; +#else + #define _TTHREAD_DISABLE_ASSIGNMENT(name) \ + name(const name&); \ + name& operator=(const name&); +#endif + +/// @def thread_local +/// Thread local storage keyword. +/// A variable that is declared with the @c thread_local keyword makes the +/// value of the variable local to each thread (known as thread-local storage, +/// or TLS). Example usage: +/// @code +/// // This variable is local to each thread. +/// thread_local int variable; +/// @endcode +/// @note The @c thread_local keyword is a macro that maps to the corresponding +/// compiler directive (e.g. @c __declspec(thread)). While the C++11 standard +/// allows for non-trivial types (e.g. classes with constructors and +/// destructors) to be declared with the @c thread_local keyword, most pre-C++11 +/// compilers only allow for trivial types (e.g. @c int). So, to guarantee +/// portable code, only use trivial types for thread local storage. +/// @note This directive is currently not supported on Mac OS X (it will give +/// a compiler error), since compile-time TLS is not supported in the Mac OS X +/// executable format. Also, some older versions of MinGW (before GCC 4.x) do +/// not support this directive. +/// @hideinitializer + +#if !defined(_TTHREAD_CPP11_) && !defined(thread_local) + #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) + #define thread_local __thread + #else + #define thread_local __declspec(thread) + #endif +#endif + + +/// Main name space for TinyThread++. +/// This namespace is more or less equivalent to the @c std namespace for the +/// C++11 thread classes. For instance, the tthread::mutex class corresponds to +/// the std::mutex class. +namespace tthread { + +/// Mutex class. +/// This is a mutual exclusion object for synchronizing access to shared +/// memory areas for several threads. The mutex is non-recursive (i.e. a +/// program may deadlock if the thread that owns a mutex object calls lock() +/// on that object). +/// @see recursive_mutex +class mutex { + public: + /// Constructor. + mutex() +#if defined(_TTHREAD_WIN32_) + : mAlreadyLocked(false) +#endif + { +#if defined(_TTHREAD_WIN32_) + InitializeCriticalSection(&mHandle); +#else + pthread_mutex_init(&mHandle, NULL); +#endif + } + + /// Destructor. + ~mutex() + { +#if defined(_TTHREAD_WIN32_) + DeleteCriticalSection(&mHandle); +#else + pthread_mutex_destroy(&mHandle); +#endif + } + + /// Lock the mutex. + /// The method will block the calling thread until a lock on the mutex can + /// be obtained. The mutex remains locked until @c unlock() is called. + /// @see lock_guard + inline void lock() + { +#if defined(_TTHREAD_WIN32_) + EnterCriticalSection(&mHandle); + while(mAlreadyLocked) Sleep(1000); // Simulate deadlock... + mAlreadyLocked = true; +#else + pthread_mutex_lock(&mHandle); +#endif + } + + /// Try to lock the mutex. + /// The method will try to lock the mutex. If it fails, the function will + /// return immediately (non-blocking). + /// @return @c true if the lock was acquired, or @c false if the lock could + /// not be acquired. + inline bool try_lock() + { +#if defined(_TTHREAD_WIN32_) + bool ret = (TryEnterCriticalSection(&mHandle) ? true : false); + if(ret && mAlreadyLocked) + { + LeaveCriticalSection(&mHandle); + ret = false; + } + return ret; +#else + return (pthread_mutex_trylock(&mHandle) == 0) ? true : false; +#endif + } + + /// Unlock the mutex. + /// If any threads are waiting for the lock on this mutex, one of them will + /// be unblocked. + inline void unlock() + { +#if defined(_TTHREAD_WIN32_) + mAlreadyLocked = false; + LeaveCriticalSection(&mHandle); +#else + pthread_mutex_unlock(&mHandle); +#endif + } + + _TTHREAD_DISABLE_ASSIGNMENT(mutex) + + private: +#if defined(_TTHREAD_WIN32_) + CRITICAL_SECTION mHandle; + bool mAlreadyLocked; +#else + pthread_mutex_t mHandle; +#endif + + friend class condition_variable; +}; + +/// Recursive mutex class. +/// This is a mutual exclusion object for synchronizing access to shared +/// memory areas for several threads. The mutex is recursive (i.e. a thread +/// may lock the mutex several times, as long as it unlocks the mutex the same +/// number of times). +/// @see mutex +class recursive_mutex { + public: + /// Constructor. + recursive_mutex() + { +#if defined(_TTHREAD_WIN32_) + InitializeCriticalSection(&mHandle); +#else + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mHandle, &attr); +#endif + } + + /// Destructor. + ~recursive_mutex() + { +#if defined(_TTHREAD_WIN32_) + DeleteCriticalSection(&mHandle); +#else + pthread_mutex_destroy(&mHandle); +#endif + } + + /// Lock the mutex. + /// The method will block the calling thread until a lock on the mutex can + /// be obtained. The mutex remains locked until @c unlock() is called. + /// @see lock_guard + inline void lock() + { +#if defined(_TTHREAD_WIN32_) + EnterCriticalSection(&mHandle); +#else + pthread_mutex_lock(&mHandle); +#endif + } + + /// Try to lock the mutex. + /// The method will try to lock the mutex. If it fails, the function will + /// return immediately (non-blocking). + /// @return @c true if the lock was acquired, or @c false if the lock could + /// not be acquired. + inline bool try_lock() + { +#if defined(_TTHREAD_WIN32_) + return TryEnterCriticalSection(&mHandle) ? true : false; +#else + return (pthread_mutex_trylock(&mHandle) == 0) ? true : false; +#endif + } + + /// Unlock the mutex. + /// If any threads are waiting for the lock on this mutex, one of them will + /// be unblocked. + inline void unlock() + { +#if defined(_TTHREAD_WIN32_) + LeaveCriticalSection(&mHandle); +#else + pthread_mutex_unlock(&mHandle); +#endif + } + + _TTHREAD_DISABLE_ASSIGNMENT(recursive_mutex) + + private: +#if defined(_TTHREAD_WIN32_) + CRITICAL_SECTION mHandle; +#else + pthread_mutex_t mHandle; +#endif + + friend class condition_variable; +}; + +/// Lock guard class. +/// The constructor locks the mutex, and the destructor unlocks the mutex, so +/// the mutex will automatically be unlocked when the lock guard goes out of +/// scope. Example usage: +/// @code +/// mutex m; +/// int counter; +/// +/// void increment() +/// { +/// lock_guard guard(m); +/// ++ counter; +/// } +/// @endcode + +template +class lock_guard { + public: + typedef T mutex_type; + + lock_guard() : mMutex(0) {} + + /// The constructor locks the mutex. + explicit lock_guard(mutex_type &aMutex) + { + mMutex = &aMutex; + mMutex->lock(); + } + + /// The destructor unlocks the mutex. + ~lock_guard() + { + if(mMutex) + mMutex->unlock(); + } + + private: + mutex_type * mMutex; +}; + +/// Condition variable class. +/// This is a signalling object for synchronizing the execution flow for +/// several threads. Example usage: +/// @code +/// // Shared data and associated mutex and condition variable objects +/// int count; +/// mutex m; +/// condition_variable cond; +/// +/// // Wait for the counter to reach a certain number +/// void wait_counter(int targetCount) +/// { +/// lock_guard guard(m); +/// while(count < targetCount) +/// cond.wait(m); +/// } +/// +/// // Increment the counter, and notify waiting threads +/// void increment() +/// { +/// lock_guard guard(m); +/// ++ count; +/// cond.notify_all(); +/// } +/// @endcode +class condition_variable { + public: + /// Constructor. +#if defined(_TTHREAD_WIN32_) + condition_variable(); +#else + condition_variable() + { + pthread_cond_init(&mHandle, NULL); + } +#endif + + /// Destructor. +#if defined(_TTHREAD_WIN32_) + ~condition_variable(); +#else + ~condition_variable() + { + pthread_cond_destroy(&mHandle); + } +#endif + + /// Wait for the condition. + /// The function will block the calling thread until the condition variable + /// is woken by @c notify_one(), @c notify_all() or a spurious wake up. + /// @param[in] aMutex A mutex that will be unlocked when the wait operation + /// starts, an locked again as soon as the wait operation is finished. + template + inline void wait(_mutexT &aMutex) + { +#if defined(_TTHREAD_WIN32_) + // Increment number of waiters + EnterCriticalSection(&mWaitersCountLock); + ++ mWaitersCount; + LeaveCriticalSection(&mWaitersCountLock); + + // Release the mutex while waiting for the condition (will decrease + // the number of waiters when done)... + aMutex.unlock(); + _wait(); + aMutex.lock(); +#else + pthread_cond_wait(&mHandle, &aMutex.mHandle); +#endif + } + + /// Notify one thread that is waiting for the condition. + /// If at least one thread is blocked waiting for this condition variable, + /// one will be woken up. + /// @note Only threads that started waiting prior to this call will be + /// woken up. +#if defined(_TTHREAD_WIN32_) + void notify_one(); +#else + inline void notify_one() + { + pthread_cond_signal(&mHandle); + } +#endif + + /// Notify all threads that are waiting for the condition. + /// All threads that are blocked waiting for this condition variable will + /// be woken up. + /// @note Only threads that started waiting prior to this call will be + /// woken up. +#if defined(_TTHREAD_WIN32_) + void notify_all(); +#else + inline void notify_all() + { + pthread_cond_broadcast(&mHandle); + } +#endif + + _TTHREAD_DISABLE_ASSIGNMENT(condition_variable) + + private: +#if defined(_TTHREAD_WIN32_) + void _wait(); + HANDLE mEvents[2]; ///< Signal and broadcast event HANDLEs. + unsigned int mWaitersCount; ///< Count of the number of waiters. + CRITICAL_SECTION mWaitersCountLock; ///< Serialize access to mWaitersCount. +#else + pthread_cond_t mHandle; +#endif +}; + + +/// Thread class. +class thread { + public: +#if defined(_TTHREAD_WIN32_) + typedef HANDLE native_handle_type; +#else + typedef pthread_t native_handle_type; +#endif + + class id; + + /// Default constructor. + /// Construct a @c thread object without an associated thread of execution + /// (i.e. non-joinable). + thread() : mHandle(0), mNotAThread(true) +#if defined(_TTHREAD_WIN32_) + , mWin32ThreadID(0) +#endif + {} + + /// Thread starting constructor. + /// Construct a @c thread object with a new thread of execution. + /// @param[in] aFunction A function pointer to a function of type: + /// void fun(void * arg) + /// @param[in] aArg Argument to the thread function. + /// @note This constructor is not fully compatible with the standard C++ + /// thread class. It is more similar to the pthread_create() (POSIX) and + /// CreateThread() (Windows) functions. + thread(void (*aFunction)(void *), void * aArg); + + /// Destructor. + /// @note If the thread is joinable upon destruction, @c std::terminate() + /// will be called, which terminates the process. It is always wise to do + /// @c join() before deleting a thread object. + ~thread(); + + /// Wait for the thread to finish (join execution flows). + /// After calling @c join(), the thread object is no longer associated with + /// a thread of execution (i.e. it is not joinable, and you may not join + /// with it nor detach from it). + void join(); + + /// Check if the thread is joinable. + /// A thread object is joinable if it has an associated thread of execution. + bool joinable() const; + + /// Detach from the thread. + /// After calling @c detach(), the thread object is no longer assicated with + /// a thread of execution (i.e. it is not joinable). The thread continues + /// execution without the calling thread blocking, and when the thread + /// ends execution, any owned resources are released. + void detach(); + + /// Return the thread ID of a thread object. + id get_id() const; + + /// Get the native handle for this thread. + /// @note Under Windows, this is a @c HANDLE, and under POSIX systems, this + /// is a @c pthread_t. + inline native_handle_type native_handle() + { + return mHandle; + } + + /// Determine the number of threads which can possibly execute concurrently. + /// This function is useful for determining the optimal number of threads to + /// use for a task. + /// @return The number of hardware thread contexts in the system. + /// @note If this value is not defined, the function returns zero (0). + static unsigned hardware_concurrency(); + + _TTHREAD_DISABLE_ASSIGNMENT(thread) + + private: + native_handle_type mHandle; ///< Thread handle. + mutable mutex mDataMutex; ///< Serializer for access to the thread private data. + bool mNotAThread; ///< True if this object is not a thread of execution. +#if defined(_TTHREAD_WIN32_) + unsigned int mWin32ThreadID; ///< Unique thread ID (filled out by _beginthreadex). +#endif + + // This is the internal thread wrapper function. +#if defined(_TTHREAD_WIN32_) + static unsigned WINAPI wrapper_function(void * aArg); +#else + static void * wrapper_function(void * aArg); +#endif +}; + +/// Thread ID. +/// The thread ID is a unique identifier for each thread. +/// @see thread::get_id() +class thread::id { + public: + /// Default constructor. + /// The default constructed ID is that of thread without a thread of + /// execution. + id() : mId(0) {}; + + id(unsigned long int aId) : mId(aId) {}; + + id(const id& aId) : mId(aId.mId) {}; + + inline id & operator=(const id &aId) + { + mId = aId.mId; + return *this; + } + + inline friend bool operator==(const id &aId1, const id &aId2) + { + return (aId1.mId == aId2.mId); + } + + inline friend bool operator!=(const id &aId1, const id &aId2) + { + return (aId1.mId != aId2.mId); + } + + inline friend bool operator<=(const id &aId1, const id &aId2) + { + return (aId1.mId <= aId2.mId); + } + + inline friend bool operator<(const id &aId1, const id &aId2) + { + return (aId1.mId < aId2.mId); + } + + inline friend bool operator>=(const id &aId1, const id &aId2) + { + return (aId1.mId >= aId2.mId); + } + + inline friend bool operator>(const id &aId1, const id &aId2) + { + return (aId1.mId > aId2.mId); + } + + inline friend std::ostream& operator <<(std::ostream &os, const id &obj) + { + os << obj.mId; + return os; + } + + private: + unsigned long int mId; +}; + + +// Related to - minimal to be able to support chrono. +typedef long long __intmax_t; + +/// Minimal implementation of the @c ratio class. This class provides enough +/// functionality to implement some basic @c chrono classes. +template <__intmax_t N, __intmax_t D = 1> class ratio { + public: + static double _as_double() { return double(N) / double(D); } +}; + +/// Minimal implementation of the @c chrono namespace. +/// The @c chrono namespace provides types for specifying time intervals. +namespace chrono { + /// Duration template class. This class provides enough functionality to + /// implement @c this_thread::sleep_for(). + template > class duration { + private: + _Rep rep_; + public: + typedef _Rep rep; + typedef _Period period; + + /// Construct a duration object with the given duration. + template + explicit duration(const _Rep2& r) : rep_(r) {}; + + /// Return the value of the duration object. + rep count() const + { + return rep_; + } + }; + + // Standard duration types. + typedef duration<__intmax_t, ratio<1, 1000000000> > nanoseconds; ///< Duration with the unit nanoseconds. + typedef duration<__intmax_t, ratio<1, 1000000> > microseconds; ///< Duration with the unit microseconds. + typedef duration<__intmax_t, ratio<1, 1000> > milliseconds; ///< Duration with the unit milliseconds. + typedef duration<__intmax_t> seconds; ///< Duration with the unit seconds. + typedef duration<__intmax_t, ratio<60> > minutes; ///< Duration with the unit minutes. + typedef duration<__intmax_t, ratio<3600> > hours; ///< Duration with the unit hours. +} + +/// The namespace @c this_thread provides methods for dealing with the +/// calling thread. +namespace this_thread { + /// Return the thread ID of the calling thread. + thread::id get_id(); + + /// Yield execution to another thread. + /// Offers the operating system the opportunity to schedule another thread + /// that is ready to run on the current processor. + inline void yield() + { +#if defined(_TTHREAD_WIN32_) + Sleep(0); +#else + sched_yield(); +#endif + } + + /// Blocks the calling thread for a period of time. + /// @param[in] aTime Minimum time to put the thread to sleep. + /// Example usage: + /// @code + /// // Sleep for 100 milliseconds + /// this_thread::sleep_for(chrono::milliseconds(100)); + /// @endcode + /// @note Supported duration types are: nanoseconds, microseconds, + /// milliseconds, seconds, minutes and hours. + template void sleep_for(const chrono::duration<_Rep, _Period>& aTime) + { +#if defined(_TTHREAD_WIN32_) + Sleep(int(double(aTime.count()) * (1000.0 * _Period::_as_double()) + 0.5)); +#else + usleep(int(double(aTime.count()) * (1000000.0 * _Period::_as_double()) + 0.5)); +#endif + } +} + +} + +// Define/macro cleanup +#undef _TTHREAD_DISABLE_ASSIGNMENT + +#endif // _TINYTHREAD_H_