diff --git a/Source/Core/CMakeLists.txt b/Source/Core/CMakeLists.txt index 02501853c9c7ecf021b97b56d08e674368f69d15..1eae5f1988a2735729de6653952bfb58ff59929f 100644 --- a/Source/Core/CMakeLists.txt +++ b/Source/Core/CMakeLists.txt @@ -320,6 +320,12 @@ target_link_libraries(${PROJECT_NAME} PRIVATE target_include_directories(${PROJECT_NAME} PUBLIC ${SRC_DIR}/Core) target_include_directories(${PROJECT_NAME} PRIVATE ${CPP_BASE64_INCLUDE_DIRS}) + +## -- IMPORTANT -- | !FOR REDUCED THREADING DEBUGGING, UNCOMMENT LINE BELOW, OTHERWISE LEAVE COMMENTED OUT! | -- IMPORTANT -- ## +# target_compile_definitions(${PROJECT_NAME} PRIVATE REDUCED_THREADING_DEBUG=1) +## -- END REDUCED THREADING COMMENT -- ## + + # Configure testing. # enable_testing() # include(GoogleTest) diff --git a/Source/Core/RPC/RPCManager.cpp b/Source/Core/RPC/RPCManager.cpp index 1a10809267895468390c66575171563717b276e4..f9cd2b097c334a56602c93bcf3926298db2922b9 100644 --- a/Source/Core/RPC/RPCManager.cpp +++ b/Source/Core/RPC/RPCManager.cpp @@ -43,6 +43,14 @@ RPCManager::RPCManager(Config::Config* _Config, BG::Common::Logger::LoggingSyste int ThreadCount = std::thread::hardware_concurrency(); + + // Reduced threaidng mode for debugging + #ifdef REDUCED_THREADING_DEBUG + ThreadCount = 1; + #endif + + + _Logger->Log("Starting RPC Server With '" + std::to_string(ThreadCount) + "' Threads", 5); // Start the RPC server asynchronously with the specified thread count diff --git a/Source/Core/VSDA/Ca/VoxelSubsystem/ArrayGeneratorPool/ArrayGeneratorPool.cpp b/Source/Core/VSDA/Ca/VoxelSubsystem/ArrayGeneratorPool/ArrayGeneratorPool.cpp index 3e0bad4e2a34bea73ef169a668c4470d44628be9..b1559bbd10b2ee69616b2fb169607e90ac916cd7 100644 --- a/Source/Core/VSDA/Ca/VoxelSubsystem/ArrayGeneratorPool/ArrayGeneratorPool.cpp +++ b/Source/Core/VSDA/Ca/VoxelSubsystem/ArrayGeneratorPool/ArrayGeneratorPool.cpp @@ -121,6 +121,11 @@ ArrayGeneratorPool::ArrayGeneratorPool(BG::Common::Logger::LoggingSystem* _Logge Logger_ = _Logger; ThreadControlFlag_ = true; + // Reduced threaidng mode for debugging + #ifdef REDUCED_THREADING_DEBUG + _NumThreads = 1; + #endif + // Create Renderer Instances Logger_->Log("Creating CAArrayGeneratorPool With " + std::to_string(_NumThreads) + " Thread(s)", 2); diff --git a/Source/Core/VSDA/Ca/VoxelSubsystem/ImageProcessorPool/ImageProcessorPool.cpp b/Source/Core/VSDA/Ca/VoxelSubsystem/ImageProcessorPool/ImageProcessorPool.cpp index 8ebacb7d57c5633223335c526262d254f660bd8d..4244b188790e3863123dd909c6ea2bcacf121f7b 100644 --- a/Source/Core/VSDA/Ca/VoxelSubsystem/ImageProcessorPool/ImageProcessorPool.cpp +++ b/Source/Core/VSDA/Ca/VoxelSubsystem/ImageProcessorPool/ImageProcessorPool.cpp @@ -242,6 +242,11 @@ ImageProcessorPool::ImageProcessorPool(BG::Common::Logger::LoggingSystem* _Logge ThreadControlFlag_ = true; + // Reduced threaidng mode for debugging + #ifdef REDUCED_THREADING_DEBUG + _NumThreads = 1; + #endif + // Create Renderer Instances Logger_->Log("Creating CAImageProcessorPool With " + std::to_string(_NumThreads) + " Thread(s)", 2); for (unsigned int i = 0; i < _NumThreads; i++) { diff --git a/Source/Core/VSDA/EM/NeuroglancerConversionPool/ConversionPool/ConversionPool.cpp b/Source/Core/VSDA/EM/NeuroglancerConversionPool/ConversionPool/ConversionPool.cpp index d74bc61e2cb67b762bcb149713106585af253f3b..4c996a9f42be50d5b0de2c3483964b910adbcb9c 100644 --- a/Source/Core/VSDA/EM/NeuroglancerConversionPool/ConversionPool/ConversionPool.cpp +++ b/Source/Core/VSDA/EM/NeuroglancerConversionPool/ConversionPool/ConversionPool.cpp @@ -62,6 +62,7 @@ double GetAverage(std::vector<double>* _Vec) { } +// Thread Main Function // Thread Main Function void ConversionPool::EncoderThreadMainFunction(int _ThreadNumber) { @@ -74,7 +75,6 @@ void ConversionPool::EncoderThreadMainFunction(int _ThreadNumber) { int SamplesBeforeUpdate = 25; std::vector<double> Times; - // Run until thread exit is requested - that is, this is set to false while (ThreadControlFlag_) { @@ -84,26 +84,50 @@ void ConversionPool::EncoderThreadMainFunction(int _ThreadNumber) { // Start Timer std::chrono::time_point Start = std::chrono::high_resolution_clock::now(); - - - // Calculate desired output filename - std::string X1 = std::to_string(Task->IndexInfo_.StartX); - std::string X2 = std::to_string(Task->IndexInfo_.EndX); - std::string Y1 = std::to_string(Task->IndexInfo_.StartY); - std::string Y2 = std::to_string(Task->IndexInfo_.EndY); - std::string Z1 = std::to_string(Task->IndexInfo_.StartZ); - std::string Z2 = std::to_string(Task->IndexInfo_.EndZ); - std::string TargetFilename = Task->OutputDirectoryBasePath_ + "/" + X1 + "-" + X2; - TargetFilename += "_" + Y1 + "-" + Y2; - TargetFilename += "_" + Z1 + "-" + Z2; - + std::string TargetFilename; // Load the source image int Width, Height, Channels; unsigned char* Image = stbi_load(Task->SourceFilePath_.c_str(), &Width, &Height, &Channels, 0); - // Now write it as a jpeg - stbi_write_jpg(TargetFilename.c_str(), Width, Height, Channels, Image, 100); + for (int ReductionLevel = 1; ReductionLevel <= Task->ReductionLevels_; ReductionLevel++) { + + // Calculate the new dimensions + int NewWidth = Width / pow(2,ReductionLevel); + int NewHeight = Height / pow(2,ReductionLevel); + + // Allocate memory for the resized image + unsigned char* ResizedImage = (unsigned char*)malloc(NewWidth * NewHeight * Channels); + + // Resize the image + stbir_resize_uint8(Image, Width, Height, 0, ResizedImage, NewWidth, NewHeight, 0, Channels); + + // Update the indexes to the scaled size + int ScaledX1 = Task->IndexInfo_.StartX / pow(2,ReductionLevel); + int ScaledX2 = Task->IndexInfo_.EndX / pow(2,ReductionLevel); + int ScaledY1 = Task->IndexInfo_.StartY / pow(2,ReductionLevel); + int ScaledY2 = Task->IndexInfo_.EndY / pow(2,ReductionLevel); + int ScaledZ1 = Task->IndexInfo_.StartZ; + int ScaledZ2 = Task->IndexInfo_.EndZ; + + // Calculate desired output filename + std::string X1 = std::to_string(ScaledX1); + std::string X2 = std::to_string(ScaledX2); + std::string Y1 = std::to_string(ScaledY1); + std::string Y2 = std::to_string(ScaledY2); + std::string Z1 = std::to_string(ScaledZ1); + std::string Z2 = std::to_string(ScaledZ2); + TargetFilename = Task->OutputDirectoryBasePath_ + "/ReductionLevel-" + std::to_string(ReductionLevel); + TargetFilename += "/" + X1 + "-" + X2; + TargetFilename += "_" + Y1 + "-" + Y2; + TargetFilename += "_" + Z1 + "-" + Z2; + + // Write the resized image as a JPEG + stbi_write_jpg(TargetFilename.c_str(), NewWidth, NewHeight, Channels, ResizedImage, 100); + + // Free the resized image memory + free(ResizedImage); + } stbi_image_free(Image); @@ -115,11 +139,10 @@ void ConversionPool::EncoderThreadMainFunction(int _ThreadNumber) { Times.push_back(Duration_ms); if (Times.size() > SamplesBeforeUpdate) { double AverageTime = GetAverage(&Times); - Logger_ ->Log("EMConversionPool Thread Info '" + std::to_string(_ThreadNumber) + "' Processed Most Recent Image '" + TargetFilename + "', Averaging " + std::to_string(AverageTime) + "ms / Image", 0); + Logger_->Log("EMConversionPool Thread Info '" + std::to_string(_ThreadNumber) + "' Processed Most Recent Image '" + TargetFilename + "', Averaging " + std::to_string(AverageTime) + "ms / Image", 0); Times.clear(); } - } else { // We didn't get any work, just go to sleep for a few milliseconds so we don't rail the cpu @@ -138,6 +161,11 @@ ConversionPool::ConversionPool(BG::Common::Logger::LoggingSystem* _Logger, int _ Logger_ = _Logger; ThreadControlFlag_ = true; + // Reduced threaidng mode for debugging + #ifdef REDUCED_THREADING_DEBUG + _NumThreads = 1; + #endif + // Create Renderer Instances Logger_->Log("Creating EMConversionPool With " + std::to_string(_NumThreads) + " Thread(s)", 2); diff --git a/Source/Core/VSDA/EM/NeuroglancerConversionPool/ConversionPool/ProcessingTask.h b/Source/Core/VSDA/EM/NeuroglancerConversionPool/ConversionPool/ProcessingTask.h index 00ab815fe01a8820c428f62471cdda16d4b961f2..2c2efbb409bb31bf4a00e78b602cff4414991bf3 100644 --- a/Source/Core/VSDA/EM/NeuroglancerConversionPool/ConversionPool/ProcessingTask.h +++ b/Source/Core/VSDA/EM/NeuroglancerConversionPool/ConversionPool/ProcessingTask.h @@ -60,6 +60,7 @@ struct ProcessingTask { std::string SourceFilePath_; /**Path where the image came from originally*/ Simulator::VoxelIndexInfo IndexInfo_; /**Information about the image's voxel positions*/ std::string OutputDirectoryBasePath_; /**Base of the path where the output is going*/ + int ReductionLevels_; /**Number of downsampling levels to do for this image */ std::atomic_bool IsDone_ = false; /**Indicates if the given task is done or not*/ diff --git a/Source/Core/VSDA/EM/NeuroglancerConversionPool/NeuroglancerConverter.cpp b/Source/Core/VSDA/EM/NeuroglancerConversionPool/NeuroglancerConverter.cpp index f360a38c9b9e2e981bf18ca2eade21f2e582837d..bc0c61c5e3a7767f7ae1006345af8082418b781d 100644 --- a/Source/Core/VSDA/EM/NeuroglancerConversionPool/NeuroglancerConverter.cpp +++ b/Source/Core/VSDA/EM/NeuroglancerConversionPool/NeuroglancerConverter.cpp @@ -50,7 +50,7 @@ namespace VSDA { -bool ExecuteConversionOperation(BG::Common::Logger::LoggingSystem* _Logger, Simulation* _Simulation, ConversionPool::ConversionPool* _ConversionPool) { +bool ExecuteConversionOperation(BG::Common::Logger::LoggingSystem* _Logger, Simulation* _Simulation, ConversionPool::ConversionPool* _ConversionPool, int _NumResolutionLevels) { // Check that the simulation has been initialized and everything is ready to have work done if (_Simulation->VSDAData_.State_ != VSDA_CONVERSION_REQUESTED) { @@ -73,7 +73,7 @@ bool ExecuteConversionOperation(BG::Common::Logger::LoggingSystem* _Logger, Simu std::string BasePath = "NeuroglancerDatasets/" + UUID + "/"; std::error_code Error; - bool Status = CreateDirectoryRecursive(BasePath + "Data", Error); + bool Status = CreateDirectoryRecursive(BasePath, Error); if (!Status) { return false; } @@ -122,39 +122,54 @@ bool ExecuteConversionOperation(BG::Common::Logger::LoggingSystem* _Logger, Simu // Create the JSON info data - // - Create the scales list - nlohmann::json Scales; - Scales["encoding"] = "jpeg"; - Scales["key"] = "Data"; + // Generate Scales List + std::vector<nlohmann::json> ScalesList; + for (int ReductionLevel = 1; ReductionLevel <= _NumResolutionLevels; ReductionLevel++) { + // - Create the scales list + nlohmann::json Scales; + Scales["encoding"] = "jpeg"; + Scales["key"] = "ReductionLevel-" + std::to_string(ReductionLevel); - // - Create the chunk sizes - std::vector<int> ChunkSizeList{Params->ImageWidth_px, Params->ImageHeight_px, 1}; - std::vector<std::vector<int>> ChunksList{ChunkSizeList}; - Scales["chunk_sizes"] = nlohmann::json(ChunksList); - // - Create the resolution sizes - int ResX_nm = Params->VoxelResolution_um * 1000; - int ResY_nm = Params->VoxelResolution_um * 1000; - int ResZ_nm = (Params->SliceThickness_um / Params->VoxelResolution_um) * 1000 * Params->VoxelResolution_um; - std::vector<int> Resolution{ResX_nm, ResY_nm, ResZ_nm}; - Scales["resolution"] = Resolution; - - // - Create the size values - int ResX_px = ceil(double(BaseRegion->RegionIndexInfo_.EndX) / double(Params->ImageWidth_px)) * Params->ImageWidth_px; - int ResY_px = ceil(double(BaseRegion->RegionIndexInfo_.EndY) / double(Params->ImageHeight_px)) * Params->ImageHeight_px; - int ResZ_px = BaseRegion->RegionIndexInfo_.EndZ; - std::vector<int> Sizes{ResX_px, ResY_px, ResZ_px}; - Scales["size"] = Sizes; - - // - Create the voxel offset values - std::vector<int> Offset{0, 0, 0}; - Scales["voxel_offset"] = Offset; + std::error_code Error; + bool Status = CreateDirectoryRecursive(BasePath + "/ReductionLevel-" + std::to_string(ReductionLevel), Error); + if (!Status) { + return false; + } + // - Create the chunk sizes + std::vector<int> ChunkSizeList{Params->ImageWidth_px / int(pow(2,ReductionLevel)), Params->ImageHeight_px / int(pow(2,ReductionLevel)), 1}; + std::vector<std::vector<int>> ChunksList{ChunkSizeList}; + Scales["chunk_sizes"] = nlohmann::json(ChunksList); + + // - Populate information about the resolution of each voxel in this output image (scaled by our total size) + int ResX_nm = Params->VoxelResolution_um * 1000; + int ResY_nm = Params->VoxelResolution_um * 1000; + int ResZ_nm = (Params->SliceThickness_um / Params->VoxelResolution_um) * 1000 * Params->VoxelResolution_um; + ResX_nm *= pow(2,ReductionLevel); // Note here that resolution means the size of each voxel in nanometres + ResY_nm *= pow(2,ReductionLevel); + // ResZ_nm *= ReductionLevel; + std::vector<int> Resolution{ResX_nm, ResY_nm, ResZ_nm}; + Scales["resolution"] = Resolution; + + // - Create the size values + int ResX_px = ceil(double(BaseRegion->RegionIndexInfo_.EndX) / double(Params->ImageWidth_px)) * Params->ImageWidth_px; + int ResY_px = ceil(double(BaseRegion->RegionIndexInfo_.EndY) / double(Params->ImageHeight_px)) * Params->ImageHeight_px; + int ResZ_px = BaseRegion->RegionIndexInfo_.EndZ; + ResX_px /= pow(2,ReductionLevel); + ResY_px /= pow(2,ReductionLevel); + // ResZ_px /= ReductionLevel; + std::vector<int> Sizes{ResX_px, ResY_px, ResZ_px}; + Scales["size"] = Sizes; + + // - Create the voxel offset values + std::vector<int> Offset{0, 0, 0}; + Scales["voxel_offset"] = Offset; + + ScalesList.push_back(Scales); + } - // Now apply it to the info dataset - std::vector<nlohmann::json> ScalesList = {Scales}; - nlohmann::json Info; Info["data_type"] = "uint8"; @@ -194,8 +209,9 @@ bool ExecuteConversionOperation(BG::Common::Logger::LoggingSystem* _Logger, Simu std::unique_ptr<ConversionPool::ProcessingTask> ThisTask = std::make_unique<ConversionPool::ProcessingTask>(); ThisTask->IndexInfo_ = BaseRegion->ImageVoxelIndexes_[i]; - ThisTask->OutputDirectoryBasePath_ = BasePath + "Data/"; + ThisTask->OutputDirectoryBasePath_ = BasePath; ThisTask->SourceFilePath_ = BaseRegion->ImageFilenames_[i]; + ThisTask->ReductionLevels_ = _NumResolutionLevels; _ConversionPool->QueueEncodeOperation(ThisTask.get()); _Simulation->VSDAData_.ConversionTasks_.push_back(std::move(ThisTask)); diff --git a/Source/Core/VSDA/EM/NeuroglancerConversionPool/NeuroglancerConverter.h b/Source/Core/VSDA/EM/NeuroglancerConversionPool/NeuroglancerConverter.h index bc536ff74ad9a2aa0f12874c56cb83229d3d0f4c..380e29dff2c53e84a0d63b910a2ea9a03d328bdf 100644 --- a/Source/Core/VSDA/EM/NeuroglancerConversionPool/NeuroglancerConverter.h +++ b/Source/Core/VSDA/EM/NeuroglancerConversionPool/NeuroglancerConverter.h @@ -63,7 +63,7 @@ namespace VSDA { * @return true Success * @return false Fail */ -bool ExecuteConversionOperation(BG::Common::Logger::LoggingSystem* _Logger, Simulation* _Simulation, ConversionPool::ConversionPool* _ConversionPool); +bool ExecuteConversionOperation(BG::Common::Logger::LoggingSystem* _Logger, Simulation* _Simulation, ConversionPool::ConversionPool* _ConversionPool, int _NumResolutionLevels = 3); diff --git a/Source/Core/VSDA/EM/VoxelSubsystem/ArrayGeneratorPool/ArrayGeneratorPool.cpp b/Source/Core/VSDA/EM/VoxelSubsystem/ArrayGeneratorPool/ArrayGeneratorPool.cpp index 8f8727a172e7764d7fef4e9f894efb05eb1d51fe..da701f15287ac5b44f3a24f495839b3cbf3976a4 100644 --- a/Source/Core/VSDA/EM/VoxelSubsystem/ArrayGeneratorPool/ArrayGeneratorPool.cpp +++ b/Source/Core/VSDA/EM/VoxelSubsystem/ArrayGeneratorPool/ArrayGeneratorPool.cpp @@ -154,6 +154,10 @@ ArrayGeneratorPool::ArrayGeneratorPool(BG::Common::Logger::LoggingSystem* _Logge Logger_ = _Logger; ThreadControlFlag_ = true; + // Reduced threaidng mode for debugging + #ifdef REDUCED_THREADING_DEBUG + _NumThreads = 1; + #endif // Create Renderer Instances Logger_->Log("Creating EMArrayGeneratorPool With " + std::to_string(_NumThreads) + " Thread(s)", 2); diff --git a/Source/Core/VSDA/EM/VoxelSubsystem/ImageProcessorPool/ImageProcessorPool.cpp b/Source/Core/VSDA/EM/VoxelSubsystem/ImageProcessorPool/ImageProcessorPool.cpp index cc68b6cdcd6a9880f3f7bc8be34b9d8ce754ba23..68d735bbb568dbe21512e8b6a8a9f5e97824c425 100644 --- a/Source/Core/VSDA/EM/VoxelSubsystem/ImageProcessorPool/ImageProcessorPool.cpp +++ b/Source/Core/VSDA/EM/VoxelSubsystem/ImageProcessorPool/ImageProcessorPool.cpp @@ -339,6 +339,11 @@ ImageProcessorPool::ImageProcessorPool(BG::Common::Logger::LoggingSystem* _Logge ThreadControlFlag_ = true; + // Reduced threaidng mode for debugging + #ifdef REDUCED_THREADING_DEBUG + _NumThreads = 1; + #endif + // Create Renderer Instances Logger_->Log("Creating EMImageProcessorPool With " + std::to_string(_NumThreads) + " Thread(s)", 2); for (unsigned int i = 0; i < _NumThreads; i++) { diff --git a/Source/Core/VSDA/EM/VoxelSubsystem/TearGenerator.cpp b/Source/Core/VSDA/EM/VoxelSubsystem/TearGenerator.cpp index 415b6201c28d069c67c1949dbe8f979c2c4cc287..b2829abae5e47db3ababdcd521005cb3c504eada 100644 --- a/Source/Core/VSDA/EM/VoxelSubsystem/TearGenerator.cpp +++ b/Source/Core/VSDA/EM/VoxelSubsystem/TearGenerator.cpp @@ -83,8 +83,16 @@ int GenerateTear(BG::Common::Logger::LoggingSystem* _Logger, std::vector<std::un StartPoint.Y = _Array->GetY() + 100; } - while ((abs(EndPoint.X - StartPoint.X) > MaxDeltaX) || (PointDistance(StartPoint, EndPoint) < MinSegmentLength) || (abs(EndPoint.Y - StartPoint.Y) > MaxDeltaY)) { + + // Prevent hangs if this is unable to generate properly due to a small output or something + int MaxIters = 1000000; + int NumIters = 0; + while ((NumIters < MaxIters) && ((abs(EndPoint.X - StartPoint.X) > MaxDeltaX) || (PointDistance(StartPoint, EndPoint) < MinSegmentLength) || (abs(EndPoint.Y - StartPoint.Y) > MaxDeltaY))) { EndPoint = GeneratePoint(PointGenerator, XVoxelDistribution, YVoxelDistribution); + NumIters++; + } + if (NumIters == MaxIters) { + _Logger->Log("Unable to generate sample tear correctly, hit iteration limit when selecting point for tear to go to, try lowering your MinSegmentLength", 8); } diff --git a/Source/Core/VSDA/RenderPool.cpp b/Source/Core/VSDA/RenderPool.cpp index b48b4ad4c2ff7ec35ca4d82b9be5d62d68a85649..271646c706db8d46fd057f0246351de7f551b595 100644 --- a/Source/Core/VSDA/RenderPool.cpp +++ b/Source/Core/VSDA/RenderPool.cpp @@ -68,7 +68,10 @@ RenderPool::RenderPool(Config::Config* _Config, BG::Common::Logger::LoggingSyste ThreadControlFlag_ = true; Windowed_ = _Windowed; - + // Reduced threaidng mode for debugging + #ifdef REDUCED_THREADING_DEBUG + _NumThreads = 1; + #endif // Setup ConversionPool int NumThreads = float(std::thread::hardware_concurrency()) * 1.5; diff --git a/Source/Core/Visualizer/ImageProcessorPool/ImageProcessorPool.cpp b/Source/Core/Visualizer/ImageProcessorPool/ImageProcessorPool.cpp index 6eb6d97ec6b4a21716ed290bf632039c51eb00b9..d3fc952993895ba6565f611edd2d64e7a8c591fe 100644 --- a/Source/Core/Visualizer/ImageProcessorPool/ImageProcessorPool.cpp +++ b/Source/Core/Visualizer/ImageProcessorPool/ImageProcessorPool.cpp @@ -131,6 +131,12 @@ ImageProcessorPool::ImageProcessorPool(BG::Common::Logger::LoggingSystem* _Logge ThreadControlFlag_ = true; + // Reduced threaidng mode for debugging + #ifdef REDUCED_THREADING_DEBUG + _NumThreads = 1; + #endif + + // Create Renderer Instances Logger_->Log("Creating VisualizerImageProcessorPool With " + std::to_string(_NumThreads) + " Thread(s)", 2); for (unsigned int i = 0; i < _NumThreads; i++) { diff --git a/Source/Core/Visualizer/VisualizerRPCInterface.cpp b/Source/Core/Visualizer/VisualizerRPCInterface.cpp index 6d8a0808d2839eb6cdb24d3736e7f4fd0d517c43..5c62a99e616f41013e13a621231e2fd503bc4355 100644 --- a/Source/Core/Visualizer/VisualizerRPCInterface.cpp +++ b/Source/Core/Visualizer/VisualizerRPCInterface.cpp @@ -110,6 +110,7 @@ std::string VisualizerRPCInterface::VisualizerGetImage(std::string _JSONRequest) i = ImageHandle.find(Pattern, i); } std::string SafeHandle = "./" + ImageHandle; + std::cout<<SafeHandle<<std::endl; // Now Check If The Handle Is Valid, If So, Load It