diff --git a/data/links.yaml b/data/links.yaml index 200bf0b72..79cf5bcf4 100644 --- a/data/links.yaml +++ b/data/links.yaml @@ -8,62 +8,62 @@ inputs: Model Library: Data: https://www.cbica.upenn.edu/CaPTk_Model_Library EGFRvIIISVMIndex: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/EGFRvIIISVMIndex.zip - Model: ftp://www.nitrc.org/home/groups/captk/downloads/models/1.8.0/EGFRvIIIIndexPredictor_PretrainedModel.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/EGFRvIIISVMIndex.zip + Model: https://captk.projects.nitrc.org/downloads/downloads/EGFRvIIIIndexPredictor_PretrainedModel.zip EGFRvIIISurrogateIndex: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/EGFRvIIISurrogateIndex.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/EGFRvIIISurrogateIndex.zip Model: N.A. DirectionalityEstimate: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/DirectionalityEstimate.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/DirectionalityEstimate.zip Model: N.A. DiffusionDerivatives: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/DiffusionDerivatives.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/DiffusionDerivatives.zip Model: N.A. TrainingModule: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/TrainingModule.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/TrainingModule.zip Model: N.A. RecurrenceEstimator: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/RecurrenceEstimator.zip - Model: ftp://www.nitrc.org/home/groups/captk/downloads/models/RecurrenceEstimator_Model.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/RecurrenceEstimator.zip + Model: https://captk.projects.nitrc.org/downloads/downloads/RecurrenceEstimator_Model.zip PseudoProgressionEstimator: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/PseudoProgressionEstimator.zip - Model: ftp://www.nitrc.org/home/groups/captk/downloads/models/1.8.0/PseudoProgressionEstimator_PretrainedModel.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/PseudoProgressionEstimator.zip + Model: https://captk.projects.nitrc.org/downloads/downloads/PseudoProgressionEstimator_PretrainedModel.zip SurvivalPredictor: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/SurvivalPredictor.zip - Model: ftp://www.nitrc.org/home/groups/captk/downloads/models/1.8.0/SurvivalPredictor_PretrainedModel.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/SurvivalPredictor.zip + Model: https://captk.projects.nitrc.org/downloads/downloads/SurvivalPredictor_PretrainedModel.zip MolecularSubtypePredictor: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/MolecularSubtypePredictor.zip - Model: ftp://www.nitrc.org/home/groups/captk/downloads/models/MolecularSubtypePredictor_Model.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/MolecularSubtypePredictor.zip + Model: https://captk.projects.nitrc.org/downloads/downloads/MolecularSubtypePredictor_Model.zip PerfusionDerivatives: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/PerfusionDerivatives.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/PerfusionDerivatives.zip Model: N.A. PerfusionPCA: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData/1.8.0/PerfusionPCA.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/PerfusionPCA.zip Model: N.A. PerfusionAlignment: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/PerfusionAlignment.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/PerfusionAlignment.zip Model: N.A. PopulationAtlases: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/PopulationAtlases.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/PopulationAtlases.zip Model: N.A. DeepMedic: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/DeepMedic.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/DeepMedic.zip Model: N.A. LungCancer: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/LungCancer.zip - Model: ftp://www.nitrc.org/home/groups/captk/downloads/models/LungCancer_Model.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/LungCancer.zip + Model: https://captk.projects.nitrc.org/downloads/downloads/LungCancer_Model.zip LIBRA: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/LIBRA.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/LIBRA.zip Model: N.A. Confetti: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/Confetti.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/Confetti.zip Model: N.A. WhiteStripe: - Data: ftp://www.nitrc.org/home/groups/captk/downloads/SampleData_1.6.0/WhiteStripe.zip + Data: https://captk.projects.nitrc.org/downloads/downloads/WhiteStripe.zip Model: N.A. hints: SoftwareRequirement: packages: CaPTk: version: - - 1.8.0 + - 1.9.0 diff --git a/src/applications/Preprocessing/src/Preprocessing.cxx b/src/applications/Preprocessing/src/Preprocessing.cxx index de8a59518..b3b54f9df 100644 --- a/src/applications/Preprocessing/src/Preprocessing.cxx +++ b/src/applications/Preprocessing/src/Preprocessing.cxx @@ -44,6 +44,7 @@ std::string inputImageFile, inputMaskFile, outputImageFile, outputDir, targetIma std::vector< std::string > inputImageFiles; // store multiple image files std::string registrationFixedImageFile, registrationType = "Affine", registrationMetrics = "NMI", registrationIterations = "100,50,5", registrationAffineTransformInput, registrationDeformableTransformInput; +std::string registrationInterp = "Linear"; int histoMatchQuantiles = 40, histoMatchBins = 100, registrationTypeInt, registrationRigidDof = 12; @@ -307,6 +308,9 @@ int algorithmsRunner() std::string commonCommands; // put all the common things for the affine/deform/reslice in single place + // add interpolation + commonCommands += " -rI " + registrationInterp; + // add iterations to command std::string iterations; { @@ -714,6 +718,7 @@ int main(int argc, char** argv) parser.addOptionalParameter("rIA", "regInterAffn", cbica::Parameter::FILE, "mat", "The path to the affine transformation to apply to moving image", "If this is present, the Affine registration step will be skipped", "Also used for rigid transformation"); parser.addOptionalParameter("rID", "regInterDefm", cbica::Parameter::FILE, "NIfTI", "The path to the deformable transformation to apply to moving image", "If this is present, the Deformable registration step will be skipped"); parser.addOptionalParameter("rsc", "rescaleImage", cbica::Parameter::STRING, "Output Intensity range", "The output intensity range after image rescaling", "Defaults to " + std::to_string(rescaleLower) + ":" + std::to_string(rescaleUpper), "If multiple inputs are passed (comma-separated), the rescaling is done in a cumulative manner,", "i.e., stats from all images are considered for the scaling"); + parser.addOptionalParameter("rIP", "regInterpolation", cbica::Parameter::STRING, "Linear or NN", "The interpolation mode to use. NN is nearest neighbor, useful for registering masks and segmentations.", "Defaults to Linear."); parser.addOptionalParameter("d", "debugMode", cbica::Parameter::BOOLEAN, "0 or 1", "Enabled debug mode", "Default: 0"); @@ -991,6 +996,15 @@ int main(int argc, char** argv) { parser.getParameterValue("rID", registrationDeformableTransformInput); } + if (parser.isPresent("rIP")) + { + parser.getParameterValue("rIP", registrationInterp); + if (!(registrationInterp == "NN" || registrationInterp == "LINEAR")) + { + std::cerr << "Requested an unsupported interpolation method. Please specify LINEAR or NN." << std::endl; + return EXIT_FAILURE; + } + } } else if (parser.isPresent("rsc")) { diff --git a/src/applications/TrainingModule.cpp b/src/applications/TrainingModule.cpp index d3419de02..bc9664849 100644 --- a/src/applications/TrainingModule.cpp +++ b/src/applications/TrainingModule.cpp @@ -39,6 +39,7 @@ See COPYING file or https://www.med.upenn.edu/cbica/captk/license.html #include #include + TrainingModule::TrainingModule() { //isCurrentlyRunning = false; @@ -87,6 +88,10 @@ const TrainingModuleResult TrainingModule::Run(const TrainingModuleParameters& p { success = false; logger.WriteError("Exception caught while running the training module. Error information: " + std::string(exc.what()) ); + logger.WriteError("Please double check the validity of your data and ensure that multiple examples exist of each class."); + result.success = false; + result.message = exc.what(); + return result; } result.success = success; @@ -115,12 +120,21 @@ bool TrainingModule::RunSplitTraining(const TrainingModuleParameters& params) std::cout << "Running training." << std::endl; VariableSizeMatrixType FeaturesOfAllSubjects; VariableLengthVectorType LabelsOfAllSubjects; + std::vector featureRowHeaders; + std::vector featureColHeaders; + std::vector labelRowHeaders; + std::vector labelColHeaders; typedef vnl_matrix MatrixType; MatrixType dataMatrix; - bool featureLoadingSucceeded = GetFeatureDataFromFile(params.inputFeaturesFile, FeaturesOfAllSubjects); - bool labelLoadingSucceeded = GetLabelsFromFile(params.inputLabelsFile, LabelsOfAllSubjects); + auto featureLoadingSucceeded = GetFeatureDataFromFile(params.inputFeaturesFile, FeaturesOfAllSubjects, featureRowHeaders, featureColHeaders); + auto labelLoadingSucceeded = GetLabelsFromFile(params.inputLabelsFile, LabelsOfAllSubjects, labelRowHeaders, labelColHeaders); + if (!std::get<0>(featureLoadingSucceeded) || !std::get<0>(labelLoadingSucceeded)) + { + std::cerr << "Error when loading training data. See logs for more information." << std::endl; + return false; + } std::cout << "Finished loading training data." << std::endl; //// Generate scaled features, save means/std-devs to disk @@ -246,6 +260,25 @@ bool TrainingModule::RunSplitTraining(const TrainingModuleParameters& params) // Perform training with the selected strategies using the previously scaled features. selectedFeatureIndices = featureSelector->PerformFeatureSelectionBasedTraining(scaledFeatureSet, labels, classifier.get()); WriteCSVFiles(selectedFeatureIndices, params.outputDirectory + "/selected-features.csv"); + if (!featureColHeaders.empty()) + { + std::ofstream myfile; + myfile.open(params.outputDirectory + "/selected-feature-names.csv"); + std::vector selectedFeatureNames; + for (int i = 0; i < selectedFeatureIndices.size(); i++) + { + std::string currentFeatureName = featureColHeaders[selectedFeatureIndices[i]]; + if (i == 0) + { + myfile << currentFeatureName; + } + else + { + myfile << "," << currentFeatureName; + } + } + myfile.close(); + } VariableSizeMatrixType selectedScaledFeatureData; selectedScaledFeatureData.SetSize(labels.size(), selectedFeatureIndices.size()); // samples x selected features @@ -341,7 +374,9 @@ bool TrainingModule::RunSplitTesting(const TrainingModuleParameters& params) } VariableSizeMatrixType allFeaturesMatrix; - bool featuresLoadingSucceeded = GetFeatureDataFromFile(params.inputFeaturesFile, allFeaturesMatrix); + std::vector featureRowHeaders; + std::vector featureColHeaders; + auto featuresLoadingSucceeded = GetFeatureDataFromFile(params.inputFeaturesFile, allFeaturesMatrix, featureRowHeaders, featureColHeaders); std::shared_ptr predictorPtr; // to be filled with the appropriate classifier for model handling @@ -425,13 +460,16 @@ bool TrainingModule::RunSplitTesting(const TrainingModuleParameters& params) // Run prediction for both classes and distances predictedLabels = predictorPtr->predict(finalFeatures); predictedDistances = predictorPtr->predict(finalFeatures, true); - WriteCSVFiles(predictedDistances, params.outputDirectory + "/predicted-distances.csv", true); - WriteCSVFiles(predictedLabels, params.outputDirectory + "/predicted-labels.csv", true); + std::vector emptyHeaders; + WriteLabelFilesWithHeaders(params.outputDirectory + "/predicted-distances.csv", predictedDistances, featureRowHeaders, emptyHeaders); + WriteLabelFilesWithHeaders(params.outputDirectory + "/predicted-labels.csv", predictedLabels, featureRowHeaders, emptyHeaders); if (params.testPredictionsAgainstProvidedLabels) { VariableLengthVectorType actualLabels; - bool succeeded = GetLabelsFromFile(params.inputLabelsFile, actualLabels); + std::vector labelRowHeaders; + std::vector labelColHeaders; + auto succeeded = GetLabelsFromFile(params.inputLabelsFile, actualLabels, labelRowHeaders, labelColHeaders); VariableLengthVectorType predictedLabelsAsItkVector; predictedLabelsAsItkVector.SetSize(predictedLabels.size()); for (unsigned int i = 0; i < predictedLabels.size(); i++) @@ -462,13 +500,21 @@ bool TrainingModule::RunKFoldCrossValidation(const TrainingModuleParameters& par std::cout << "Loading data." << std::endl; VariableSizeMatrixType FeaturesOfAllSubjects; VariableLengthVectorType LabelsOfAllSubjects; + std::vector featureRowHeaders; + std::vector featureColHeaders; + std::vector labelRowHeaders; + std::vector labelColHeaders; typedef vnl_matrix MatrixType; MatrixType dataMatrix; - bool featureLoadingSucceeded = GetFeatureDataFromFile(params.inputFeaturesFile, FeaturesOfAllSubjects); - bool labelLoadingSucceeded = GetLabelsFromFile(params.inputLabelsFile, LabelsOfAllSubjects); + auto featureLoadingSucceeded = GetFeatureDataFromFile(params.inputFeaturesFile, FeaturesOfAllSubjects, featureRowHeaders, featureColHeaders); + auto labelLoadingSucceeded = GetLabelsFromFile(params.inputLabelsFile, LabelsOfAllSubjects, labelRowHeaders, labelColHeaders); auto folds = params.folds; + if (folds <= LabelsOfAllSubjects.Size() * 2 ) + { + throw std::runtime_error("Fold size too great for the amount of samples -- ensure your data has enough samples, and contains examples of each class."); + } std::vector sampleIndices; for (int i = 0; i < LabelsOfAllSubjects.Size(); i++) { @@ -486,6 +532,8 @@ bool TrainingModule::RunKFoldCrossValidation(const TrainingModuleParameters& par VariableSizeMatrixType testingData; VectorDouble trainingLabels; VectorDouble testingLabels; + std::vector testingSubjectHeaders; + std::vector trainingSubjectHeaders; trainingData.SetSize(trainGroupSize, FeaturesOfAllSubjects.Cols()); testingData.SetSize(testGroupSize, FeaturesOfAllSubjects.Cols()); @@ -494,6 +542,7 @@ bool TrainingModule::RunKFoldCrossValidation(const TrainingModuleParameters& par { int testGroupIndex = i + testGroupStartIndex; testingLabels.push_back(LabelsOfAllSubjects[sampleIndices[testGroupIndex]]); + testingSubjectHeaders.push_back(featureRowHeaders[sampleIndices[testGroupIndex]]); for (int j = 0; j < FeaturesOfAllSubjects.Cols(); j++) { @@ -503,6 +552,7 @@ bool TrainingModule::RunKFoldCrossValidation(const TrainingModuleParameters& par for (int i = 0; i < testGroupStartIndex; i++) // copy first part of training data samples { trainingLabels.push_back(LabelsOfAllSubjects[i]); + trainingSubjectHeaders.push_back(featureRowHeaders[sampleIndices[i]]); for (int j = 0; j < FeaturesOfAllSubjects.Cols(); j++) { @@ -512,6 +562,7 @@ bool TrainingModule::RunKFoldCrossValidation(const TrainingModuleParameters& par for (int i = testGroupStartIndex + testGroupSize; i < sampleIndices.size(); i++) // copy second part of training data samples { trainingLabels.push_back(LabelsOfAllSubjects[i]); + trainingSubjectHeaders.push_back(featureRowHeaders[sampleIndices[i]]); for (int j = 0; j < FeaturesOfAllSubjects.Cols(); j++) { @@ -525,11 +576,11 @@ bool TrainingModule::RunKFoldCrossValidation(const TrainingModuleParameters& par { throw std::runtime_error("Couldn't create the fold subdirectory during cross-validation mode."); } - // Need to use vertical param of WriteCSV files to get the labels in the form we accept. This will need reworking. - WriteCSVFiles(trainingData, outputFoldDir + "/raw-training-features.csv"); - WriteCSVFiles(trainingLabels, outputFoldDir + "/training-labels.csv", true); - WriteCSVFiles(testingData, outputFoldDir + "/raw-testing-features.csv"); - WriteCSVFiles(testingLabels, outputFoldDir + "/ground-truth-labels.csv", true); + + WriteNumericFilesWithHeaders(outputFoldDir + "/raw-training-features.csv", trainingData, trainingSubjectHeaders, featureColHeaders); + WriteLabelFilesWithHeaders(outputFoldDir + "/training-labels.csv", trainingLabels, trainingSubjectHeaders, labelColHeaders); + WriteNumericFilesWithHeaders(outputFoldDir + "/raw-testing-features.csv", testingData, testingSubjectHeaders, featureColHeaders); + WriteLabelFilesWithHeaders(outputFoldDir + "/ground-truth-labels.csv", testingLabels, testingSubjectHeaders, labelColHeaders); // Run training on this fold's training data auto thisFoldParams = params; @@ -557,15 +608,18 @@ bool TrainingModule::RunKFoldCrossValidation(const TrainingModuleParameters& par return true; } -void TrainingModule::GetHeaderInformationFromFile(std::string filename) + +std::tuple TrainingModule::GetFeatureDataFromFile(std::string featuresFilename, VariableSizeMatrixType& featuresMatrix, std::vector& rowHeaders, std::vector& colHeaders) { - // To be used for detecting headers from FE/etc. - // TODO: Implement this (potentially alongside "labels-in-features-file" functionality to allow one-file training) -} + // Return value : + // first is true if successful overall + // second is true if row headers were present (also populates if true) + // third is true if column headers were present (also populates if true) + std::tuple result = std::make_tuple(false, false, false); + bool hasRowHeaders = false; + bool hasColumnHeaders = false; -bool TrainingModule::GetFeatureDataFromFile(std::string featuresFilename, VariableSizeMatrixType& featuresMatrix) -{ MatrixType dataMatrix; try { @@ -576,6 +630,49 @@ bool TrainingModule::GetFeatureDataFromFile(std::string featuresFilename, Variab readerMean->HasRowHeadersOff(); readerMean->Parse(); dataMatrix = readerMean->GetArray2DDataObject()->GetMatrix(); + + // Check row and column header existence via heuristic: presence of all-NaNs + + for (int row = 0; row < dataMatrix.rows(); row++) + { + if (!std::isnan(dataMatrix[row][0])) + { + // Found a number in row headers, must assume this column meant to be numerical (i.e. no row headers). + hasRowHeaders = false; + break; + } + hasRowHeaders = true; // All entries in the first column are Not A Number + } + + for (int col = 0; col < dataMatrix.cols(); col++) + { + if (!std::isnan(dataMatrix[0][col])) + { + // Found a number in column headers, must assume this row meant to be numerical (i.e. no column headers). + hasColumnHeaders = false; + break; + } + hasColumnHeaders = true;// All entries in the first row are Not A Number + } + + if (hasRowHeaders) + { + readerMean->HasRowHeadersOn(); + } + if (hasColumnHeaders) + { + readerMean->HasColumnHeadersOn(); + } + // Re-parse with this new knowledge if headers are present at all + if (hasRowHeaders || hasColumnHeaders) + { + readerMean->Parse(); + dataMatrix = readerMean->GetArray2DDataObject()->GetMatrix(); + } + + colHeaders = readerMean->GetArray2DDataObject()->GetColumnHeaders(); + rowHeaders = readerMean->GetArray2DDataObject()->GetRowHeaders(); + featuresMatrix.SetSize(dataMatrix.rows(), dataMatrix.columns()); for (unsigned int i = 0; i < dataMatrix.rows(); i++) @@ -584,15 +681,17 @@ bool TrainingModule::GetFeatureDataFromFile(std::string featuresFilename, Variab } catch (const std::exception& e1) { - std::cerr << "Error reading the feature file in the input directory. Error code : " + std::string(e1.what()) << std::endl; - return false; + std::cerr << "Error reading the feature file in the input directory. Check permissions and existence. Error code : " + std::string(e1.what()) << std::endl; + return std::make_tuple(false, false, false); } - return true; + return std::make_tuple(true, hasRowHeaders, hasColumnHeaders); } -bool TrainingModule::GetLabelsFromFile(std::string labelsFilename, VariableLengthVectorType& labelsVector) +std::tuple TrainingModule::GetLabelsFromFile(std::string labelsFilename, VariableLengthVectorType& labelsVector, std::vector& rowHeaders, std::vector& colHeaders) { MatrixType dataMatrix; + bool hasRowHeaders = false; + bool hasColumnHeaders = false; try { CSVFileReaderType::Pointer readerMean = CSVFileReaderType::New(); @@ -602,6 +701,49 @@ bool TrainingModule::GetLabelsFromFile(std::string labelsFilename, VariableLengt readerMean->HasRowHeadersOff(); readerMean->Parse(); dataMatrix = readerMean->GetArray2DDataObject()->GetMatrix(); + + // Check row and column header existence via heuristic: presence of all-NaNs + + for (int row = 0; row < dataMatrix.rows(); row++) + { + if (!std::isnan(dataMatrix[row][0])) + { + // Found a number in row headers, must assume this column meant to be numerical (i.e. no row headers). + hasRowHeaders = false; + break; + } + hasRowHeaders = true; // All entries in the first column are Not A Number + } + + for (int col = 0; col < dataMatrix.cols(); col++) + { + if (!std::isnan(dataMatrix[0][col])) + { + // Found a number in column headers, must assume this row meant to be numerical (i.e. no column headers). + hasColumnHeaders = false; + break; + } + hasColumnHeaders = true;// All entries in the first row are Not A Number + } + + if (hasRowHeaders) + { + readerMean->HasRowHeadersOn(); + } + if (hasColumnHeaders) + { + readerMean->HasColumnHeadersOn(); + } + // Re-parse with this new knowledge if headers are present at all + if (hasRowHeaders || hasColumnHeaders) + { + readerMean->Parse(); + dataMatrix = readerMean->GetArray2DDataObject()->GetMatrix(); + } + + colHeaders = readerMean->GetArray2DDataObject()->GetColumnHeaders(); + rowHeaders = readerMean->GetArray2DDataObject()->GetRowHeaders(); + labelsVector.SetSize(dataMatrix.rows()); for (unsigned int i = 0; i < dataMatrix.rows(); i++) @@ -609,10 +751,10 @@ bool TrainingModule::GetLabelsFromFile(std::string labelsFilename, VariableLengt } catch (const std::exception& e1) { - std::cerr << "Error reading the labels file in the input directory. Error code : " + std::string(e1.what()) << std::endl; - return false; + std::cerr << "Error reading the labels file in the input directory. Check permissions and existence. Error code : " + std::string(e1.what()) << std::endl; + return std::make_tuple(false, false, false); } - return true; + return std::make_tuple(true, hasRowHeaders, hasColumnHeaders); } double TrainingModule::GetBinaryClassificationBalancedAccuracy(VariableLengthVectorType& predictedLabels, VariableLengthVectorType& actualLabels) @@ -669,6 +811,109 @@ void TrainingModule::RemoveNans(VariableLengthVectorType& vec) } } +void TrainingModule::WriteNumericFilesWithHeaders(const std::string& filePath, const VariableSizeMatrixType& data, const std::vector& rowHeaders, const std::vector& colHeaders) +{ + std::ofstream myfile; + myfile.open(filePath); + + if (rowHeaders.size() != data.Rows()) + { + throw std::logic_error("Header/data row mismatch!"); + } + if (colHeaders.size() != data.Cols()) + { + throw std::logic_error("Header/data column mismatch!"); + } + + // First write out the whole set of column headers if needed + if (!colHeaders.empty()) + { + myfile << " ,"; // filler so we don't misalign + for (unsigned int i = 0; i < colHeaders.size(); i++) + { + if (i == 0) + myfile << colHeaders[i]; + else + myfile << "," << colHeaders[i]; + } + myfile << "\n"; + } + + bool useRowHeaders = true; + if (rowHeaders.empty()) + { + useRowHeaders = false; + } + + // Now write data with row headers at start if needed + for (unsigned int index1 = 0; index1 < data.Rows(); index1++) + { + if (useRowHeaders) + { + myfile << rowHeaders[index1] << ","; + } + + for (unsigned int index2 = 0; index2 < data.Cols(); index2++) + { + if (index2 == 0) + myfile << std::to_string(data[index1][index2]); + else + myfile << "," << std::to_string(data[index1][index2]); + } + myfile << "\n"; + } + myfile.close(); +} + +void TrainingModule::WriteLabelFilesWithHeaders(const std::string& filePath, const VectorDouble& data, const std::vector& rowHeaders, const std::vector& colHeaders) +{ + std::ofstream myfile; + myfile.open(filePath); + + if (!rowHeaders.empty() && (rowHeaders.size() != data.size())) + { + throw std::logic_error("Header/data row mismatch!"); + } + + if (colHeaders.size() > 1) + { + throw std::logic_error("Header/data column mismatch!"); + } + + // First write out the whole set of column headers if needed + if (!colHeaders.empty()) + { + myfile << " ,"; // filler so we don't misalign + for (unsigned int i = 0; i < colHeaders.size(); i++) + { + if (i == 0) + myfile << colHeaders[i]; + else + myfile << "," << colHeaders[i]; + } + myfile << "\n"; + } + + bool useRowHeaders = true; + if (rowHeaders.empty()) + { + useRowHeaders = false; + } + + // Now write data with row headers at start if needed + for (unsigned int index1 = 0; index1 < data.size(); index1++) + { + if (useRowHeaders) + { + myfile << rowHeaders[index1] << ","; + } + + myfile << data[index1]; + myfile << "\n"; + } + myfile.close(); +} + /*void TrainingModule::RunThread(const TrainingModuleParameters& params) { @@ -703,4 +948,4 @@ void TrainingModule::onThreadFinished() { isCurrentlyRunning = false; emit done(lastResult); -}*/ \ No newline at end of file +}*/ diff --git a/src/applications/TrainingModule.h b/src/applications/TrainingModule.h index e8c5059cc..b3ae75b99 100644 --- a/src/applications/TrainingModule.h +++ b/src/applications/TrainingModule.h @@ -84,18 +84,32 @@ class TrainingModule : public QObject void RemoveNans(VariableSizeMatrixType& mat); void RemoveNans(VariableLengthVectorType& vec); + void WriteNumericFilesWithHeaders(const std::string& filePath, const VariableSizeMatrixType& data, const std::vector& rowHeaders, const std::vector& colHeaders); + void WriteLabelFilesWithHeaders(const std::string& filePath, const VectorDouble& data, const std::vector& rowHeaders, const std::vector& colHeaders); + + // given a set of predicted (binary-classifier) labels and actual labels, returns the balanced accuracy. static double GetBinaryClassificationBalancedAccuracy(VariableLengthVectorType& predicted, VariableLengthVectorType& actual); - static void GetHeaderInformationFromFile(std::string filename); + //static void GetHeaderInformationFromFile(std::string filename); // param featuresMatrix : the matrix to load features into - // returns True if successful, false otherwise - static bool GetFeatureDataFromFile(std::string featuresFilename, VariableSizeMatrixType& featuresMatrix); + // param rowHeaders: std vector to load row headers into + // param colHeaders: std vector to load column headers into + // returns 3-tuple of bools: + // First is overall success (true if success, failure otherwise) + // Second is true if row headers are present + // Third is true if column headers are present + static std::tuple GetFeatureDataFromFile(std::string featuresFilename, VariableSizeMatrixType& featuresMatrix, std::vector& rowHeaders, std::vector& colHeaders); // param labelsVector : the vector to load labels into - // returns True if successful, false otherwise - static bool GetLabelsFromFile(std::string labelsFilename, VariableLengthVectorType& labelsVector); + // param rowHeaders: std vector to load row headers into + // param colHeaders: std vector to load column headers into + // returns 3-tuple of bools: + // First is overall success (true if success, failure otherwise) + // Second is true if row headers are present + // Third is true if column headers are present + static std::tuple GetLabelsFromFile(std::string labelsFilename, VariableLengthVectorType& labelsVector, std::vector& rowHeaders, std::vector& colHeaders); //public slots: //void onThreadFinished(); diff --git a/src/view/gui/fMainWindow.cpp b/src/view/gui/fMainWindow.cpp index 972352fbf..4cee9df96 100644 --- a/src/view/gui/fMainWindow.cpp +++ b/src/view/gui/fMainWindow.cpp @@ -384,7 +384,7 @@ fMainWindow::fMainWindow() } // TBD: this needs to be controlled from CMake and not hard-coded here - std::string brainAppList = " EGFRvIIISVMIndex EGFRvIIISurrogateIndex RecurrenceEstimator PseudoProgressionEstimator"; + std::string brainAppList = " EGFRvIIISVMIndex EGFRvIIISurrogateIndex RecurrenceEstimator PseudoProgressionEstimator "; #ifdef BUILD_MSUBTYPE //brainAppList += " MolecularSubtypePredictor "; #endif @@ -523,9 +523,9 @@ fMainWindow::fMainWindow() connect(featurePanel, SIGNAL(helpClicked_FeaUsage(std::string)), this, SLOT(help_contextual(std::string))); connect(®istrationPanel, - SIGNAL(RegistrationSignal(std::string, std::vector, std::vector, std::vector, std::string, bool, bool, bool, std::string, std::string, std::string)), + SIGNAL(RegistrationSignal(std::string, std::vector, std::vector, std::vector, std::string, bool, bool, bool, std::string, std::string, std::string, std::string)), this, - SLOT(Registration(std::string, std::vector, std::vector, std::vector, std::string, bool, bool, bool, std::string, std::string, std::string))); + SLOT(Registration(std::string, std::vector, std::vector, std::vector, std::string, bool, bool, bool, std::string, std::string, std::string, std::string))); cbica::createDir(loggerFolder); m_tempFolderLocation = loggerFolder + "tmp_" + cbica::getCurrentProcessID(); @@ -9532,7 +9532,7 @@ void fMainWindow::GeodesicTrainingFinishedWithErrorHandler(QString errorMessage) void fMainWindow::Registration(std::string fixedFileName, std::vector inputFileNames, std::vector outputFileNames, std::vector matrixFileNames, std::string metrics, bool rigidMode, bool affineMode, bool deformMode, - std::string radii, std::string iterations, std::string degreesOfFreedom) + std::string radii, std::string iterations, std::string degreesOfFreedom, std::string interp) { std::string configPathName; std::string configFileName; @@ -9590,6 +9590,9 @@ void fMainWindow::Registration(std::string fixedFileName, std::vector inputFileNames, std::vector outputFileNames, std::vector matrixFileNames, std::string metrics, bool rigidMode, bool affineMode, bool deformMode, - std::string radii, std::string iterations, std::string degreesOfFreedom); + std::string radii, std::string iterations, std::string degreesOfFreedom, std::string interp); //confirm before exit void closeEvent(QCloseEvent * event); diff --git a/src/view/gui/fRegistrationDialog.cpp b/src/view/gui/fRegistrationDialog.cpp index 56d0a8f2a..25735f23c 100644 --- a/src/view/gui/fRegistrationDialog.cpp +++ b/src/view/gui/fRegistrationDialog.cpp @@ -103,6 +103,9 @@ fRegistrationDialog::fRegistrationDialog() connect(options_MetricSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(SelectedMetric(int))); connect(nccRadii, SIGNAL(textChanged(QString)), this, SLOT(setRadii(QString))); connect(iterations, SIGNAL(textChanged(QString)), this, SLOT(getIterations(QString))); + connect(interpolation_LINEAR, SIGNAL(clicked()), this, SLOT(SelectedLinearInterp())); + connect(interpolation_NEARESTNEIGHBOR, SIGNAL(clicked()), this, SLOT(SelectedNNInterp())); + } @@ -630,6 +633,15 @@ void fRegistrationDialog::SelectedDeformMode() degreesOfFreedom->setEnabled(false); } +void fRegistrationDialog::SelectedLinearInterp() +{ + interp = "LINEAR"; +} + +void fRegistrationDialog::SelectedNNInterp() +{ + interp = "NN"; +} void fRegistrationDialog::SelectedMetric(int index) { switch (options_MetricSelector->currentIndex()) @@ -884,7 +896,7 @@ void fRegistrationDialog::ConfirmButtonPressed() metric, options_RIGID_selected->isChecked(), options_AFFINE_selected->isChecked(), - options_DEFORMABLE_selected->isChecked(), radius, m_iterations, degreesOfFreedom->text().toStdString()); + options_DEFORMABLE_selected->isChecked(), radius, m_iterations, degreesOfFreedom->text().toStdString(), interp); this->close(); } } diff --git a/src/view/gui/fRegistrationDialog.h b/src/view/gui/fRegistrationDialog.h index ad3a2fc74..8d45fa4f1 100644 --- a/src/view/gui/fRegistrationDialog.h +++ b/src/view/gui/fRegistrationDialog.h @@ -36,6 +36,7 @@ class fRegistrationDialog : public QDialog, private Ui::fRegistrationDialog std::string metric = "NMI"; std::string radius = "5x5x5"; std::string m_iterations = "100x50x5"; + std::string interp = "Linear"; bool radii = false; int clicked = 0; QString matExtn = ".mat"; @@ -65,6 +66,8 @@ class fRegistrationDialog : public QDialog, private Ui::fRegistrationDialog void SelectedAffineMode(); void SelectedRigidMode(); void SelectedDeformMode(); + void SelectedLinearInterp(); + void SelectedNNInterp(); void SelectedMetric(int index); void addMoreImages(); void SelectGenerateMatrix(bool checked); @@ -79,7 +82,7 @@ class fRegistrationDialog : public QDialog, private Ui::fRegistrationDialog std::vector matrixfilenames, std::string metrics, bool rigidMode, bool affineMode, bool deformMode, - std::string radii, std::string iterations, std::string dof); + std::string radii, std::string iterations, std::string dof, std::string interp); }; #endif \ No newline at end of file diff --git a/src/view/gui/fTrainingDialog.cpp b/src/view/gui/fTrainingDialog.cpp index e3147f383..dc7ddbd04 100644 --- a/src/view/gui/fTrainingDialog.cpp +++ b/src/view/gui/fTrainingDialog.cpp @@ -443,7 +443,7 @@ void fTrainingSimulator::ConfirmButtonPressed() } else { - ShowMessage("Training module finished!", this); + ShowMessage("Training module finished! Please check the output folder for results.", this); } } diff --git a/src/view/gui/ui_fRegistrationDialog.h b/src/view/gui/ui_fRegistrationDialog.h index 744465f68..414011def 100644 --- a/src/view/gui/ui_fRegistrationDialog.h +++ b/src/view/gui/ui_fRegistrationDialog.h @@ -132,6 +132,12 @@ class ui_fRegistrationDialog QRadioButton *options_RIGID_selected; QRadioButton *options_DEFORMABLE_selected; + QGroupBox* interpolationGroupBox; + QHBoxLayout* interpolationLayout; + + QRadioButton* interpolation_LINEAR; + QRadioButton* interpolation_NEARESTNEIGHBOR; + QPushButton *confirmButton; QPushButton *cancelButton; QPushButton *resetButton; @@ -160,6 +166,10 @@ class ui_fRegistrationDialog modeGroupBox = new QGroupBox(fRegistrationDialog); modeGroupBox->setTitle(QString::fromStdString("Registration Mode:")); + interpolationGroupBox = new QGroupBox(fRegistrationDialog); + interpolationGroupBox->setTitle("Interpolation"); + interpolationGroupBox->setToolTip("Linear is useful for most cases. Nearest neigbor is useful for masks and segmentations."); + matrixGroupBox = new QGroupBox(fRegistrationDialog); matrixGroupBox->setTitle(QString::fromStdString("Transformation Matrix:")); @@ -187,6 +197,9 @@ class ui_fRegistrationDialog registrationGridLayout = new QGridLayout(registrationGroupBox); registrationGridLayout->setObjectName(QString::fromUtf8("registrationGridLayout")); + interpolationLayout = new QHBoxLayout(interpolationGroupBox); + registrationGridLayout->setObjectName(QString::fromUtf8("interpolationLayout")); + inputGridLayout = new QGridLayout(inputGroupBox); inputGridLayout->setObjectName(QString::fromUtf8("inputGridLayout")); @@ -215,6 +228,13 @@ class ui_fRegistrationDialog modeGridLayout->addWidget(options_RIGID_selected, gridRowCounter, 2, 1, 1); modeGridLayout->addWidget(options_DEFORMABLE_selected, gridRowCounter, 3, 1, 1); + /*--------- Interp ------------*/ + interpolation_LINEAR = new QRadioButton("Linear", interpolationGroupBox); + interpolation_NEARESTNEIGHBOR = new QRadioButton("Nearest Neighbor", interpolationGroupBox); + interpolation_NEARESTNEIGHBOR->setToolTip("Useful for registering segmentations and masks."); + interpolationLayout->addWidget(interpolation_LINEAR); + interpolationLayout->addWidget(interpolation_NEARESTNEIGHBOR); + /*---------------------Metrics---------------------*/ options_MetricSelector_label = new QLabel(registrationGroupBox); options_MetricSelector_label->setObjectName("metric"); @@ -640,11 +660,12 @@ class ui_fRegistrationDialog // put the layout in perspective gridLayout->addWidget(modeGroupBox, 0, 0, 1, 2); - gridLayout->addWidget(registrationGroupBox, 1, 0, 1, 2); - gridLayout->addWidget(inputGroupBox, 2, 0, 1, 2); - gridLayout->addWidget(matrixGroupBox, 3, 0, 1, 2); - gridLayout->addWidget(outputGroupBox, 4, 0, 1, 2); - gridLayout->addWidget(buttonGroupBox, 5, 0, 1, 2); + gridLayout->addWidget(interpolationGroupBox, 1, 0, 1, 2); + gridLayout->addWidget(registrationGroupBox, 2, 0, 1, 2); + gridLayout->addWidget(inputGroupBox, 3, 0, 1, 2); + gridLayout->addWidget(matrixGroupBox, 4, 0, 1, 2); + gridLayout->addWidget(outputGroupBox, 5, 0, 1, 2); + gridLayout->addWidget(buttonGroupBox, 6, 0, 1, 2); /*------------------------Buttons-------------------------------*/ confirmButton = new QPushButton(buttonGroupBox);