diff --git a/Modules/DICOM/CMakeLists.txt b/Modules/DICOM/CMakeLists.txt index 7a333dad07..4af61879f1 100644 --- a/Modules/DICOM/CMakeLists.txt +++ b/Modules/DICOM/CMakeLists.txt @@ -1,11 +1,12 @@ MITK_CREATE_MODULE( DEPENDS MitkCore PACKAGE_DEPENDS PUBLIC tinyxml2 PRIVATE DCMTK|dcmdata+ofstd ITK|IOGDCM TARGET_DEPENDS PUBLIC gdcmMSFF ) add_subdirectory(test) add_subdirectory(autoload/DICOMImageIO) +add_subdirectory(cmdapps) \ No newline at end of file diff --git a/Modules/DICOM/cmdapps/CMakeLists.txt b/Modules/DICOM/cmdapps/CMakeLists.txt new file mode 100644 index 0000000000..b02aa8fddc --- /dev/null +++ b/Modules/DICOM/cmdapps/CMakeLists.txt @@ -0,0 +1,8 @@ +option(BUILD_DICOMCmdApps "Build command-line apps of the MitkDICOM module" OFF) + +if(BUILD_DICOMCmdApps OR MITK_BUILD_ALL_APPS) + mitkFunctionCreateCommandLineApp( + NAME DICOMVolumeDiagnostics + DEPENDS MitkDICOM + ) +endif() diff --git a/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp b/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp new file mode 100644 index 0000000000..b5f253bf47 --- /dev/null +++ b/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp @@ -0,0 +1,195 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include + + +#include +#include +#include +#include + +#include + +#include + +void InitializeCommandLineParser(mitkCommandLineParser& parser) +{ + parser.setTitle("DICOM Volume Diagnostics"); + parser.setCategory("DICOM"); + parser.setDescription("Gives insights how MITK readers would convert a set of DICOM files into image volumes (e.g. number of volumes and the sorting of the files)"); + parser.setContributor("German Cancer Research Center (DKFZ)"); + parser.setArgumentPrefix("--", "-"); + + parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); + parser.addArgument("only-own-series", "s", mitkCommandLineParser::Bool, "Only own series", "Analyze only files in the same directory that have the same DICOM Series UID, if a file is provided as input.", us::Any()); + parser.addArgument("check-3D", "d", mitkCommandLineParser::Bool, "Check 3D configs", "Analyze the input by using all known 3D configurations. If flag is not set all configurations (3D and 3D+t) will be used.", us::Any()); + parser.addArgument("check-3D+t", "t", mitkCommandLineParser::Bool, "Check 3D+t configs", "Analyze the input by using all known 3D+t configurations (thus dynamic image configurations). If flag is not set all configurations (3D and 3D+t) will be used.", us::Any()); + parser.addArgument("input", "i", mitkCommandLineParser::File, "Input file or path", "Input contour(s)", us::Any(), false, false, false, mitkCommandLineParser::Input); + parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file", "Output file where the diagnostics results are stored as json.", us::Any()); +} + +int main(int argc, char* argv[]) +{ + int returnValue = EXIT_SUCCESS; + + mitkCommandLineParser parser; + InitializeCommandLineParser(parser); + + auto args = parser.parseArguments(argc, argv); + + if (args.empty()) + return EXIT_FAILURE; + + nlohmann::json diagnosticsResult; + + try + { + auto inputFilename = us::any_cast(args["input"]); + auto outputFilename = args.count("output")==0 ? std::string() : us::any_cast(args["output"]); + bool onlyOwnSeries = args.count("only-own-series"); + bool check3D = args.count("check-3D"); + bool check3DPlusT = args.count("check-3D+t"); + + if (!check3D && !check3DPlusT) + { //if no check option is selected all are activated by default. + check3D = true; + check3DPlusT = true; + } + + diagnosticsResult["input"] = inputFilename; + diagnosticsResult["only-own-series"] = onlyOwnSeries; + diagnosticsResult["check-3D"] = check3D; + diagnosticsResult["check-3D+t"] = check3DPlusT; + + mitk::StringList relevantFiles = mitk::GetDICOMFilesInSameDirectory(inputFilename); + + if (relevantFiles.empty()) + { + mitkThrow() << "DICOM Volume Diagnostics found no relevant files in specified location. No data is loaded. Location: " << inputFilename; + } + else + { + bool pathIsDirectory = std::filesystem::is_directory(std::filesystem::path(inputFilename)); + + if (!pathIsDirectory && onlyOwnSeries) + { + relevantFiles = mitk::FilterDICOMFilesForSameSeries(inputFilename, relevantFiles); + } + + diagnosticsResult["analyzed_files"] = relevantFiles; + + auto selector = mitk::DICOMFileReaderSelector::New(); + + if (check3D) selector->LoadBuiltIn3DConfigs(); + if (check3DPlusT) selector->LoadBuiltIn3DnTConfigs(); + + nlohmann::json readerInfos; + for (const auto reader : selector->GetAllConfiguredReaders()) + { + nlohmann::json readerInfo; + readerInfo["class_name"] = reader->GetNameOfClass(); + readerInfo["configuration_label"] = reader->GetConfigurationLabel(); + readerInfo["configuration_description"] = reader->GetConfigurationDescription(); + readerInfos.emplace_back(readerInfo); + } + diagnosticsResult["checked_readers"] = readerInfos; + + selector->SetInputFiles(relevantFiles); + + auto reader = selector->GetFirstReaderWithMinimumNumberOfOutputImages(); + + if (reader.IsNull()) + { + mitkThrow() << "DICOM Volume Diagnostics service found no suitable reader configuration for relevant files."; + } + else + { + nlohmann::json readerInfo; + readerInfo["class_name"] = reader->GetNameOfClass(); + readerInfo["configuration_label"] = reader->GetConfigurationLabel(); + readerInfo["configuration_description"] = reader->GetConfigurationDescription(); + readerInfo["configuration_description"] = reader->GetConfigurationDescription(); + std::stringstream config; + reader->PrintConfiguration(config); + readerInfo["config_details"] = config.str(); + + diagnosticsResult["selected_reader"] = readerInfo; + + nlohmann::json outputInfos; + + unsigned int relevantOutputCount = 0; + const auto nrOfOutputs = reader->GetNumberOfOutputs(); + for (unsigned int outputIndex = 0; outputIndex < nrOfOutputs; ++outputIndex) + { + bool isRelevantOutput = true; + if (!pathIsDirectory) + { + const auto frameList = reader->GetOutput(outputIndex).GetImageFrameList(); + auto finding = std::find_if(frameList.begin(), frameList.end(), [&](const mitk::DICOMImageFrameInfo::Pointer& frame) + { + std::filesystem::path framePath(frame->Filename); + std::filesystem::path inputPath(inputFilename); + return framePath == inputPath; + }); + isRelevantOutput = finding != frameList.end(); + } + + if (isRelevantOutput) + { + relevantOutputCount++; + nlohmann::json outputInfo; + + const auto output = reader->GetOutput(outputIndex); + const auto frameList = output.GetImageFrameList(); + mitk::DICOMFilePathList outputFiles; + outputFiles.resize(frameList.size()); + std::transform(frameList.begin(), frameList.end(), outputFiles.begin(), [](const mitk::DICOMImageFrameInfo::Pointer& frame) { return frame->Filename; }); + + outputInfo["files"] = outputFiles; + outputInfo["timesteps"] = output.GetNumberOfTimeSteps(); + outputInfo["frames_per_timesteps"] = output.GetNumberOfFramesPerTimeStep(); + outputInfos.emplace_back(outputInfo); + } + } + diagnosticsResult["volume_count"] = relevantOutputCount; + diagnosticsResult["volumes"] = outputInfos; + } + } + std::cout << std::endl << "### DIAGNOSTICS REPORT ###" << std::endl << std::endl; + std::cout << std::setw(2) << diagnosticsResult << std::endl; + + if (!outputFilename.empty()) + { + std::ofstream fileout(outputFilename); + fileout << diagnosticsResult; + fileout.close(); + } + + } + catch (const mitk::Exception& e) + { + MITK_ERROR << e.GetDescription(); + return EXIT_FAILURE; + } + catch (const std::exception& e) + { + MITK_ERROR << e.what(); + return EXIT_FAILURE; + } + catch (...) + { + return EXIT_FAILURE; + } + + return returnValue; +} diff --git a/Modules/DICOM/include/mitkDICOMFilesHelper.h b/Modules/DICOM/include/mitkDICOMFilesHelper.h index 355b30834c..a561cc8689 100644 --- a/Modules/DICOM/include/mitkDICOMFilesHelper.h +++ b/Modules/DICOM/include/mitkDICOMFilesHelper.h @@ -1,41 +1,41 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkDICOMFilesHelper_h #define mitkDICOMFilesHelper_h #include #include #include namespace mitk { typedef std::vector DICOMFilePathList; /** Helper functions. Searches for all files in the directory of the passed file path. All files will be checked if they are DICOM files. All DICOM files will be added to the result and returned. @remark The helper does no sorting of any kind.*/ -DICOMFilePathList GetDICOMFilesInSameDirectory(const std::string& filePath); +DICOMFilePathList MITKDICOM_EXPORT GetDICOMFilesInSameDirectory(const std::string& filePath); /** All passed files will be checked if they are DICOM files. All DICOM files will be added to the result and returned. @remark The helper does no sorting of any kind.*/ -DICOMFilePathList FilterForDICOMFiles(const DICOMFilePathList& fileList); +DICOMFilePathList MITKDICOM_EXPORT FilterForDICOMFiles(const DICOMFilePathList& fileList); /** Returns all DICOM files passed with fileList that have the same series instance UID then the passed refFilePath. @pre refFilePath must point to a valid DICOM file.*/ -DICOMFilePathList FilterDICOMFilesForSameSeries(const std::string& refFilePath, const DICOMFilePathList& fileList); +DICOMFilePathList MITKDICOM_EXPORT FilterDICOMFilesForSameSeries(const std::string& refFilePath, const DICOMFilePathList& fileList); } #endif