diff --git a/CMake/cvutilInstallTargets.cmake b/CMake/cvutilInstallTargets.cmake index 9e01b54..93b97d2 100644 --- a/CMake/cvutilInstallTargets.cmake +++ b/CMake/cvutilInstallTargets.cmake @@ -155,10 +155,17 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows") elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") # Install the RhizoVisionExplorer executable - install(TARGETS RhizoVisionExplorer rv - CONFIGURATIONS Debug Release - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - ) + if(Qt6_FOUND) + install(TARGETS RhizoVisionExplorer rv + CONFIGURATIONS Debug Release + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) + else() + install(TARGETS rv + CONFIGURATIONS Debug Release + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) + endif() # Install additional files. install(FILES README.md COPYING CONFIGURATIONS Debug Release DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/RhizoVisionExplorer) diff --git a/CMakeLists.txt b/CMakeLists.txt index 91f4917..cbf60af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,14 +89,22 @@ endif() find_package(OpenCV REQUIRED) # Find Qt6 -find_package(Qt6 REQUIRED COMPONENTS Core Widgets Charts Gui OpenGL) -qt_standard_project_setup() +find_package(Qt6 COMPONENTS Core Widgets Charts Gui OpenGL) + +if(Qt6_found) + qt_standard_project_setup() + add_compile_definitions(BUILD_GUI) +else() + message(WARNING "qt6 not found, building without GUI support.") +endif() # Find cvutil find_package(cvutil REQUIRED) -if (CMAKE_SYSTEM_NAME STREQUAL "Linux") - find_package(X11 REQUIRED) +if(Qt6_FOUND) + if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_package(X11 REQUIRED) + endif() endif() get_cmake_property(_variableNames VARIABLES) @@ -116,7 +124,6 @@ set(CMAKE_AUTOUIC ON) # Enable automatic UIC # Define the source files for RoiManager set(SOURCES - RhizoVisionExplorer/MainUI.cpp RhizoVisionExplorer/feature_extraction.cpp RhizoVisionExplorer/rootsegmentprop.cpp RhizoVisionExplorer/roottopology.cpp @@ -128,8 +135,6 @@ set(HEADERS RhizoVisionExplorer/feature_extraction.h RhizoVisionExplorer/feature_config.h RhizoVisionExplorer/common_types.h - RhizoVisionExplorer/demo.h - RhizoVisionExplorer/MainUI.h RhizoVisionExplorer/resource.h RhizoVisionExplorer/rootsegmentprop.h RhizoVisionExplorer/roottopology.h @@ -140,29 +145,50 @@ if(WIN32) list(APPEND SOURCES RhizoVisionExplorer/RhizoVisionExplorer.rc) endif() -# Define the target as an executable. -add_executable(RhizoVisionExplorer WIN32 - RhizoVisionExplorer/main.cpp - ${SOURCES} - ${HEADERS}) -add_executable(rv +if(Qt6_found) + list(APPEND SOURCES RhizoVisionExplorer/MainUI.cpp) + list(APPEND HEADERS RhizoVisionExplorer/demo.h RhizoVisionExplorer/MainUI.h) +endif() + +if(Qt6_found) + # Define the target as an executable. + add_executable(RhizoVisionExplorer WIN32 + RhizoVisionExplorer/main.cpp + ${SOURCES} + ${HEADERS}) + set_target_properties(RhizoVisionExplorer PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) + # Add the generated resource .cpp file to the target + target_sources(RhizoVisionExplorer PRIVATE ${RESOURCES}) + + # Include directories (Qt and OpenCV paths are already handled at the solution level) + target_include_directories(RhizoVisionExplorer + PRIVATE + #${CMAKE_BINARY_DIR}/library/include + ${OpenCV_INCLUDE_DIRS} + ) + # Link libraries (cvutil depends on PluginManager and RoiManager) + target_link_libraries(RhizoVisionExplorer + PRIVATE + ${OpenCV_LIBS} + Qt6::Core + Qt6::Widgets + Qt6::Charts + Qt6::Gui + Qt6::OpenGL + cvutil::cvutil + cvutil::PluginManager + cvutil::RoiManager + ) +endif() + +add_executable(rv RhizoVisionExplorer/rv.cpp RhizoVisionExplorer/indicators/indicators.hpp ${SOURCES} ${HEADERS}) -set_target_properties(RhizoVisionExplorer PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) -set_target_properties(rv PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) - -# Add the generated resource .cpp file to the target -target_sources(RhizoVisionExplorer PRIVATE ${RESOURCES}) -# Include directories (Qt and OpenCV paths are already handled at the solution level) -target_include_directories(RhizoVisionExplorer - PRIVATE - #${CMAKE_BINARY_DIR}/library/include - ${OpenCV_INCLUDE_DIRS} -) +set_target_properties(rv PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) target_include_directories(rv PRIVATE @@ -170,31 +196,10 @@ target_include_directories(rv ${OpenCV_INCLUDE_DIRS} ) -# Link libraries (cvutil depends on PluginManager and RoiManager) -target_link_libraries(RhizoVisionExplorer - PRIVATE - ${OpenCV_LIBS} - Qt6::Core - Qt6::Widgets - Qt6::Charts - Qt6::Gui - Qt6::OpenGL - cvutil::cvutil - cvutil::PluginManager - cvutil::RoiManager -) - target_link_libraries(rv PRIVATE ${OpenCV_LIBS} - Qt6::Core - Qt6::Widgets - Qt6::Charts - Qt6::Gui - Qt6::OpenGL cvutil::cvutil - cvutil::PluginManager - cvutil::RoiManager ) set(CPACK_PROPERTIES_FILE "${CMAKE_BINARY_DIR}/RhizoVisionCPackProperties-$.cmake") diff --git a/README.md b/README.md index caf58b8..7444d25 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,13 @@ -# ![RhiZoVision Explorer Logo](./RVElogoclearback-80.png) RhizoVision Explorer +# Information about this fork + +Modified source code to remove dependencies on Qt6. Needs an appropriate +[cvutil](https://github.com/sotlampr/cvutil). + +TODO: +- Better replace Q* clases with std equivalents +- Use RoiManager (uses QtGraphicsRectItem for calculations) + +## ![RhiZoVision Explorer Logo](./RVElogoclearback-80.png) RhizoVision Explorer RhizoVision Explorer is free and open-source software developed for estimating root traits from images acquired from a flatbed scanner or camera. Root images are expected to have a high contrast of roots with the background, homogenous lighting, and minimal overlapping of roots. The software is built in C++ for speed and stability, using QT for the graphical user interface and OpenCV for image processing. Traits extracted by RhizoVision Explorer have been extensively validated using a physical copper wire ground truth image set, thousands of simulated roots, and comparisons with other image analysis software. The default “Broken Roots” mode is meant for roots washed out from soil cores or pots that are disconnected and provides length, volume, branching frequency, among other features, with the ability to bin measurements based on diameter thresholds. The “Whole Root” mode extracts additional root system architecture features of more intact root systems, especially excavated root crowns or rhizoboxes, such as the convex hull, angles, and holes. The software supports multiple regions of interest, batch mode, and user-defined export of processed images with overlaid features for use in reports. diff --git a/RhizoVisionExplorer/feature_config.h b/RhizoVisionExplorer/feature_config.h index b4abbc9..b05b606 100644 --- a/RhizoVisionExplorer/feature_config.h +++ b/RhizoVisionExplorer/feature_config.h @@ -34,7 +34,12 @@ If not, see . #include #include +#ifdef BUILD_GUI #include +#else +#include +typedef std::string QString; +#endif #define RHIZOVISION_EXPLORER_VERSION "2.5.0 Beta" diff --git a/RhizoVisionExplorer/feature_extraction.cpp b/RhizoVisionExplorer/feature_extraction.cpp index 1e76e4f..74c466c 100644 --- a/RhizoVisionExplorer/feature_extraction.cpp +++ b/RhizoVisionExplorer/feature_extraction.cpp @@ -868,8 +868,10 @@ static bool checkProgress(feature_config *config, QString str) { if (!config->consolemode) { +#ifdef BUILD_GUI config->ui->updateProgress(str); QApplication::processEvents(); +#endif return config->abortprocess; } // else diff --git a/RhizoVisionExplorer/feature_extraction.h b/RhizoVisionExplorer/feature_extraction.h index b8b1f5b..bcdff8d 100644 --- a/RhizoVisionExplorer/feature_extraction.h +++ b/RhizoVisionExplorer/feature_extraction.h @@ -44,7 +44,11 @@ If not, see . //using namespace cv; using namespace std; +#ifdef BUILD_GUI #include "MainUI.h" +#else +#include "feature_config.h" +#endif void feature_extractor(feature_config *config); diff --git a/RhizoVisionExplorer/rootsegmentprop.cpp b/RhizoVisionExplorer/rootsegmentprop.cpp index d7de700..b587589 100644 --- a/RhizoVisionExplorer/rootsegmentprop.cpp +++ b/RhizoVisionExplorer/rootsegmentprop.cpp @@ -97,9 +97,12 @@ void getrootlength_new(Mat skeleton, ListofListsRef segments, PointList o for (int i = 0; i < segsize; i++) { simplified = doughlas_peucker(segments[i], 0.71, false); // We took the 0.71 as threshold to make sure we identify straight lines. - segmentsize = simplified.size() - 1; + segmentsize = simplified.size(); - for (int j = 0; j < segmentsize; j++) + if (segmentsize == 0) + continue; + + for (int j = 0; j < segmentsize-1; j++) { diff = simplified[j] - simplified[j + 1]; rootlen += norm(diff); diff --git a/RhizoVisionExplorer/roottopology.cpp b/RhizoVisionExplorer/roottopology.cpp index 358e01d..5d95db4 100644 --- a/RhizoVisionExplorer/roottopology.cpp +++ b/RhizoVisionExplorer/roottopology.cpp @@ -990,6 +990,11 @@ void getroottopology(Mat &_skeleton, Mat dist, } } } + + if (segment != nullptr) { + delete segment; + segment = nullptr; + } //window("new1", skeleton.rowRange(3729 - 50, 3729 + 50).colRange(3400 - 50, 3400 + 50)); diff --git a/RhizoVisionExplorer/rv.cpp b/RhizoVisionExplorer/rv.cpp index b27e0e7..437b250 100644 --- a/RhizoVisionExplorer/rv.cpp +++ b/RhizoVisionExplorer/rv.cpp @@ -42,7 +42,19 @@ If not, see . #include #include +#ifdef BUILD_GUI +// Not yet supported as RoiManager depends on QGraphicsRectItem #include +#define TO_STD_STRING(x) (x).toStdString() +#define FROM_STD_STRING(x) QString::fromStdString(x) +#else +#include +typedef std::ofstream QTextStream; +typedef std::vector QStringList; +#define TO_STD_STRING(x) x +#define FROM_STD_STRING(x) x +#define qSetRealNumberPrecision(x) std::setprecision(x) +#endif #include "indicators/indicators.hpp" @@ -127,6 +139,7 @@ static void append_checked_positive_ascending(double v, std::vector &out "Values for --dranges must be ascending. Got " + std::to_string(v) + " after " + std::to_string(out.back())); } + out.push_back(v); } // Returns true if token was a drange token (consumed); false means “not a drange token, stop.” @@ -248,6 +261,7 @@ feature_config parseCommandLine(int argc, char* argv[]) config.showHelp = true; } } +#ifdef BUILD_GUI else if (arg == "--roipath") { if (i + 1 < argc) @@ -268,7 +282,7 @@ feature_config parseCommandLine(int argc, char* argv[]) } RoiManager* mgr = RoiManager::GetInstance(); - mgr->loadAnnotation(QString::fromStdString(roipath)); + mgr->loadAnnotation(FROM_STD_STRING(roipath)); } else { @@ -276,6 +290,7 @@ feature_config parseCommandLine(int argc, char* argv[]) config.showHelp = true; } } +#endif else if (arg == "--metafile") { if (i + 1 < argc) @@ -715,7 +730,9 @@ void printUsage(const char* programName) std::cout << "RhizoVision Command Line Interface\n\n"; std::cout << "Usage: " << programName << " [OPTIONS] \n\n"; std::cout << "Arguments:\n"; +#ifdef BUILD_GUI std::cout << " --roipath PATH Path to CSV file containing ROI annotations (optional)\n"; +#endif std::cout << " --metafile FILE Path to metadata CSV file (optional)\n"; // FIXME: Not implemented yet std::cout << " input_path Path to image file or directory containing images\n\n"; @@ -837,6 +854,7 @@ bool analyzeImage(feature_config* config) // Set image name for reporting config->imagefilename = fs::path(config->inputPath).filename().string(); +#ifdef BUILD_GUI // Get ROI info RoiManager* mgr = RoiManager::GetInstance(); int roicount = mgr->getROICount(); @@ -902,6 +920,7 @@ bool analyzeImage(feature_config* config) config->processed = outputs; } else +#endif { // Call the feature extraction function config->input = input; @@ -912,7 +931,7 @@ bool analyzeImage(feature_config* config) // Save segmented image if requested if (config->savesegmented && !config->seg.empty()) { - std::string savefile = (fs::path(config->inputPath).stem().string() + config->segsuffix.toStdString() + ".png"); + std::string savefile = (fs::path(config->inputPath).stem().string() + TO_STD_STRING(config->segsuffix) + ".png"); std::string segFilename = (fs::path(config->outputPath) / savefile).string(); cv::imwrite(segFilename, config->seg); if (config->verbose) @@ -922,7 +941,7 @@ bool analyzeImage(feature_config* config) // Save processed image if requested if (config->saveprocessed && !config->processed.empty()) { - std::string savefile = (fs::path(config->inputPath).stem().string() + config->prosuffix.toStdString() + ".png"); + std::string savefile = (fs::path(config->inputPath).stem().string() + TO_STD_STRING(config->prosuffix) + ".png"); std::string proFilename = (fs::path(config->outputPath) / savefile).string(); cv::imwrite(proFilename, config->processed); if (config->verbose) @@ -998,20 +1017,24 @@ void writeResultsToCsv(feature_config &config, if (!config.noappend && fileExists && config.verbose) std::cerr << "Warning: Output file " << csvFullPath << " already exists. Appending results." << std::endl; +#ifdef BUILD_GUI QFile outf(csvFullPath); if (!outf.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { std::cerr << "Error: Could not open output file " << csvFullPath << std::endl; return; } - QTextStream f(&outf); +#else + std::ofstream f; + f.open(csvFullPath, std::ios::binary | std::ios::app); +#endif if (!fileExists || config.noappend) writeCsvHeader(config, f); for (size_t i = 0; i < imageFiles.size(); ++i) { const auto &features = allFeatures[i]; - const QString filename = QString::fromStdString(fs::path(imageFiles[i].toStdString()).filename().string()); + const QString filename = FROM_STD_STRING(fs::path(TO_STD_STRING(imageFiles[i])).filename().string()); const QString roiname = roiNames[i]; f << filename << "," << roiname; @@ -1027,8 +1050,11 @@ void writeResultsToCsv(feature_config &config, f << "\n"; } - +#ifdef BUILD_GUI outf.close(); +#else + f.close(); +#endif } std::tuple getEstimatedTimeRemaining(int currentImageIndex, int totalImages, double elapsedTime) @@ -1134,8 +1160,10 @@ int main(int argc, char *argv[]) config.consolemode = true; +#ifdef BUILD_GUI RoiManager *mgr = RoiManager::GetInstance(); int roicount = mgr->getROICount(); +#endif // Set time start for processing auto start = std::chrono::steady_clock::now(); @@ -1182,20 +1210,22 @@ int main(int argc, char *argv[]) if (analyzeImage(&config)) { +#ifdef BUILD_GUI if (roicount > 0) { for (int r = 0; r < roicount; r++) { std::string roiName = mgr->getROIName(r); allFeatures.push_back(config.roifeatures[r]); - processedFiles.push_back(QString::fromStdString(imageFiles[i])); - roinames.push_back(QString::fromStdString(roiName)); + processedFiles.push_back(FROM_STD_STRING(imageFiles[i])); + roinames.push_back(FROM_STD_STRING(roiName)); } } else +#endif { allFeatures.push_back(config.features); - processedFiles.push_back(QString::fromStdString(imageFiles[i])); + processedFiles.push_back(FROM_STD_STRING(imageFiles[i])); roinames.push_back("Full"); } }