ArrayXcd MP2RAGE::signal(const double M0, const double T1, const double B1, const double eta) const { return One_MP2RAGE(m_flip, m_TR, m_N, m_TD, M0, T1, B1, eta); }
int main(int argc, char **argv) { args::ArgumentParser parser( "Calculates T1/B1 maps from MP2/3-RAGE data\nhttp://github.com/spinicist/QUIT"); args::Positional<std::string> input_path(parser, "INPUT FILE", "Path to complex MP-RAGE data"); args::HelpFlag help(parser, "HELP", "Show this help message", {'h', "help"}); args::Flag verbose(parser, "VERBOSE", "Print more information", {'v', "verbose"}); args::ValueFlag<int> threads(parser, "THREADS", "Use N threads (default=4, 0=hardware limit)", {'T', "threads"}, QI::GetDefaultThreads()); args::ValueFlag<std::string> outarg( parser, "OUTPREFIX", "Add a prefix to output filenames", {'o', "out"}); args::ValueFlag<std::string> json_file( parser, "FILE", "Read JSON input from file instead of stdin", {"file"}); args::ValueFlag<float> beta_arg( parser, "BETA", "Regularisation factor for robust contrast calculation " "(https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0099676)", {'b', "beta"}, 0.0); args::Flag t1(parser, "T1", "Calculate T1 map via spline look-up", {'t', "t1"}); QI::ParseArgs(parser, argc, argv, verbose, threads); auto inFile = QI::ReadImage<QI::SeriesXF>(QI::CheckPos(input_path), verbose); auto ti_1 = itk::ExtractImageFilter<QI::SeriesXF, QI::VolumeXF>::New(); auto ti_2 = itk::ExtractImageFilter<QI::SeriesXF, QI::VolumeXF>::New(); auto region = inFile->GetLargestPossibleRegion(); region.GetModifiableSize()[3] = 0; ti_1->SetExtractionRegion(region); ti_1->SetDirectionCollapseToSubmatrix(); ti_1->SetInput(inFile); region.GetModifiableIndex()[3] = 1; ti_2->SetExtractionRegion(region); ti_2->SetDirectionCollapseToSubmatrix(); ti_2->SetInput(inFile); QI::Log(verbose, "Generating MP2 contrasts"); using BinaryFilter = itk::BinaryGeneratorImageFilter<QI::VolumeXF, QI::VolumeXF, QI::VolumeF>; auto MP2Filter = BinaryFilter::New(); MP2Filter->SetInput1(ti_1->GetOutput()); MP2Filter->SetInput2(ti_2->GetOutput()); const float &beta = beta_arg.Get(); MP2Filter->SetFunctor([&](const std::complex<float> &p1, const std::complex<float> &p2) { return MP2Contrast(p1, p2, beta); }); MP2Filter->Update(); const std::string out_prefix = outarg ? outarg.Get() : QI::StripExt(input_path.Get()); QI::WriteImage(MP2Filter->GetOutput(), out_prefix + "_MP2" + QI::OutExt(), verbose); if (t1) { QI::Log(verbose, "Reading sequence information"); rapidjson::Document input = json_file ? QI::ReadJSON(json_file.Get()) : QI::ReadJSON(std::cin); QI::MP2RAGESequence mp2rage_sequence(input["MP2RAGE"]); QI::Log(verbose, "Building look-up spline"); int num_entries = 100; Eigen::ArrayXd T1_values = Eigen::ArrayXd::LinSpaced(num_entries, 0.25, 4.0); Eigen::ArrayXd MP2_values(num_entries); for (int i = 0; i < num_entries; i++) { const auto sig = One_MP2RAGE(1., T1_values[i], 1., mp2rage_sequence); const float mp2 = MP2Contrast(sig[0], sig[1]); if ((i > 0) && (mp2 > MP2_values[i - 1])) { num_entries = i; break; } else { MP2_values[i] = mp2; } } QI::Log(verbose, "Lookup table length = {}", num_entries); QI::SplineInterpolator mp2_to_t1(MP2_values.head(num_entries), T1_values.head(num_entries)); if (beta) { QI::Log(verbose, "Recalculating unregularised MP2 image"); MP2Filter->SetFunctor( [&](const std::complex<float> &p1, const std::complex<float> &p2) { return MP2Contrast(p1, p2, 0.0); }); MP2Filter->Update(); } using UnaryFilter = itk::UnaryGeneratorImageFilter<QI::VolumeF, QI::VolumeF>; auto T1LookupFilter = UnaryFilter::New(); T1LookupFilter->SetInput(MP2Filter->GetOutput()); auto lookup = [&](const float &p) { return mp2_to_t1(p); }; T1LookupFilter->SetFunctor(lookup); QI::Log(verbose, "Calculating T1"); T1LookupFilter->Update(); QI::WriteImage(T1LookupFilter->GetOutput(), out_prefix + "_MP2_T1" + QI::OutExt(), verbose); } QI::Log(verbose, "Finished."); return EXIT_SUCCESS; }