diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..7f747f863 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,63 @@ +FROM nvidia/cuda:12.2.2-runtime-ubuntu22.04 +ARG TAG_ANTS +RUN apt-get update && apt-get full-upgrade -y && \ + apt-get install --no-install-recommends -y \ + software-properties-common \ + unzip \ + curl \ + wget \ + make \ + git \ + libboost-all-dev \ + zlib1g-dev \ + ca-certificates \ + qt6-base-dev \ + qt6-base-dev-tools \ + qt6-base-private-dev \ + qt6-tools-dev \ + qt6-tools-dev-tools \ + qt6-l10n-tools \ + libqt6charts6-dev \ + libqt6opengl6-dev \ + libzip-dev \ + mesa-common-dev \ + libglu1-mesa-dev \ + build-essential \ + gnupg \ + bc \ + ninja-build \ + apt-transport-https && \ + wget -O /tmp/kitware-archive.key https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null && \ + gpg --dearmor /tmp/kitware-archive.key && \ + mv /tmp/kitware-archive.key.gpg /usr/share/keyrings/kitware-archive-keyring.gpg && \ + echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ jammy main" > /etc/apt/sources.list.d/kitware.list && \ + apt-get update && \ + apt-get -y install cmake cmake-data && \ + update-alternatives --install /usr/bin/qmake qmake /usr/lib/qt6/bin/qmake6 100 && \ + apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +COPY . /opt/dsi-studio/src +RUN cd /opt/dsi-studio \ + && sed -i '/color_bar_dialog/d;/filebrowser/d;/mac_filesystem/d' src/dsi_studio.pro \ + && sed -i 's/DSI_STUDIO_LOGIN/""/g' src/mainwindow.cpp \ + && mkdir build && cd build \ + && qmake ../src/dsi_studio.pro && make -j 1 + +ARG ATLAS_SHA=59a1ca250845e569813ca6a2c5702d74f4d4cb4a +ARG UNET_SHA=3ec6aa9513c0c8bbb90399a7af16245b594cd09b + +RUN cd /opt/dsi-studio \ + && mv build/dsi_studio . \ + && chmod 755 dsi_studio \ + && cp -R src/other/* . \ + && rm -rf src build \ + && curl -sSLO https://github.com/frankyeh/UNet-Studio-Data/archive/${UNET_SHA}.zip \ + && unzip ${UNET_SHA}.zip \ + && rm ${UNET_SHA}.zip \ + && mv UNet-Studio-Data-${UNET_SHA}/network/ . \ + && rm -rf UNet-Studio-Data-${UNET_SHA} \ + && curl -sSLO https://github.com/data-others/atlas/archive/${ATLAS_SHA}.zip \ + && unzip ${ATLAS_SHA}.zip \ + && rm -rf DSI-Studio-atlas-${ATLAS_SHA}/.git \ + && mv atlas-${ATLAS_SHA} atlas \ + && rm ${ATLAS_SHA}.zip diff --git a/cmd/ana.cpp b/cmd/ana.cpp index 493d6952a..17b6e8da0 100644 --- a/cmd/ana.cpp +++ b/cmd/ana.cpp @@ -673,7 +673,8 @@ int ana_tract(tipl::program_option& po,std::shared_ptr hand } } if(tipl::ends_with(output,".trk.gz") || - tipl::ends_with(output,".tt.gz")) + tipl::ends_with(output,".tt.gz") || + tipl::ends_with(output,".trx")) { tipl::out() << "saving multiple tracts into one file: " << output; if(!TractModel::save_all(output,tracts)) diff --git a/cmd/trk.cpp b/cmd/trk.cpp index acf30ca37..8abe21bb2 100644 --- a/cmd/trk.cpp +++ b/cmd/trk.cpp @@ -487,7 +487,7 @@ int trk_post(tipl::program_option& po, if(output_track) { - if(!tipl::ends_with(tract_file_name,{".tt.gz",".trk.gz",".trk",".tck",".txt",".mat"})) + if(!tipl::ends_with(tract_file_name,{".tt.gz",".trx",".trk.gz",".trk",".tck",".txt",".mat"})) tract_file_name += "." + po.get("trk_format","tt.gz"); bool failed = false; if(po.has("ref")) // save track in T1W/T2W space diff --git a/dsi_studio.pro b/dsi_studio.pro new file mode 100644 index 000000000..aae336a35 --- /dev/null +++ b/dsi_studio.pro @@ -0,0 +1,191 @@ +QT += core \ + gui \ + opengl \ + charts \ + network + +greaterThan(QT_MAJOR_VERSION, 5): QT += widgets +equals(QT_MAJOR_VERSION, 6): DEFINES += QT6_PATCH + +CONFIG += c++17 +# CONFIG += console + +TARGET = dsi_studio +TEMPLATE = app + +INCLUDEPATH += ./plot +DEFINES += TIPL_USE_QT + +linux* { +QMAKE_CXXFLAGS += -fpermissive -Wno-sign-compare +CONFIG += link_pkgconfig +PKGCONFIG += libzip eigen3 +PKGCONFIG += spdlog fmt +LIBS += -lGLU -lz -lzip -lspdlog -lfmt +} + +win32* { +INCLUDEPATH += ../include $$[QT_INSTALL_HEADERS]/QtZlib +QMAKE_CXXFLAGS += -wd4244 -wd4267 -wd4018 +LIBS += -lOpenGL32 -lGlu32 +RC_ICONS = dsi_studio.ico +} + +mac { +QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.15 +INCLUDEPATH += /Users/admin/include +ICON = dsi_studio.icns +LIBS += -lz +} + +INCLUDEPATH += libs \ + libs/dsi \ + libs/tracking \ + libs/mapping \ + dicom \ + /home/matt/projects/trx_dsi_studio/DSI-Studio-TRX/trx-cpp/include + +HEADERS += mainwindow.h \ + console.h \ + dicom/dicom_parser.h \ + dicom/dwi_header.hpp \ + libs/dsi/tessellated_icosahedron.hpp \ + libs/dsi/odf_process.hpp \ + libs/dsi/image_model.hpp \ + libs/dsi/gqi_process.hpp \ + libs/dsi/gqi_mni_reconstruction.hpp \ + libs/dsi/dti_process.hpp \ + libs/dsi/basic_voxel.hpp \ + SliceModel.h \ + tracking/tracking_window.h \ + reconstruction/reconstruction_window.h \ + tracking/slice_view_scene.h \ + opengl/glwidget.h \ + opengl/renderingtablewidget.h \ + opengl/tract_render.hpp \ + opengl/region_render.hpp \ + libs/tracking/tracking_method.hpp \ + libs/tracking/roi.hpp \ + libs/tracking/fib_data.hpp \ + libs/tracking/basic_process.hpp \ + libs/tracking/tract_cluster.hpp \ + tracking/region/regiontablewidget.h \ + tracking/region/Regions.h \ + libs/tracking/tract_model.hpp \ + tracking/tract/tracttablewidget.h \ + qcolorcombobox.h \ + libs/tracking/tracking_thread.hpp \ + libs/mapping/atlas.hpp \ + view_image.h \ + manual_alignment.h \ + tracking/tract_report.hpp \ + tracking/connectivity_matrix_dialog.h \ + tracking/atlasdialog.h \ + qcompletelineedit.h \ + libs/mapping/connectometry_db.hpp \ + connectometry/createdbdialog.h \ + connectometry/match_db.h \ + connectometry/db_window.h \ + connectometry/group_connectometry.hpp \ + connectometry/group_connectometry_analysis.h \ + regtoolbox.h \ + auto_track.h \ + tracking/device.h \ + tracking/devicetablewidget.h \ + libs/dsi/hist_process.hpp \ + xnat_dialog.h \ + /home/matt/projects/trx_dsi_studio/DSI-Studio-TRX/trx-cpp/include/trx/trx.h \ + /home/matt/projects/trx_dsi_studio/DSI-Studio-TRX/trx-cpp/include/trx/trx.tpp \ + /home/matt/projects/trx_dsi_studio/DSI-Studio-TRX/trx-cpp/include/trx/filesystem.h + +FORMS += mainwindow.ui \ + console.ui \ + tracking/tracking_window.ui \ + reconstruction/reconstruction_window.ui \ + dicom/dicom_parser.ui \ + view_image.ui \ + manual_alignment.ui \ + tracking/tract_report.ui \ + tracking/connectivity_matrix_dialog.ui \ + tracking/atlasdialog.ui \ + connectometry/createdbdialog.ui \ + connectometry/match_db.ui \ + connectometry/db_window.ui \ + connectometry/group_connectometry.ui \ + regtoolbox.ui \ + auto_track.ui \ + xnat_dialog.ui + +RESOURCES += \ + icons.qrc + +SOURCES += main.cpp \ + console.cpp \ + mainwindow.cpp \ + dicom/dicom_parser.cpp \ + dicom/dwi_header.cpp \ + cmd/img.cpp \ + libs/dsi/dsi_interface_imp.cpp \ + libs/dsi/gqi_process.cpp \ + libs/tracking/tract_cluster.cpp \ + SliceModel.cpp \ + tracking/tracking_window.cpp \ + tracking/tracking_window_action.cpp \ + reconstruction/reconstruction_window.cpp \ + tracking/slice_view_scene.cpp \ + opengl/glwidget.cpp \ + opengl/tract_render.cpp \ + opengl/region_render.cpp \ + opengl/renderingtablewidget.cpp \ + tracking/region/regiontablewidget.cpp \ + tracking/region/Regions.cpp \ + libs/tracking/tract_model.cpp \ + tracking/tract/tracttablewidget.cpp \ + qcolorcombobox.cpp \ + cmd/trk.cpp \ + cmd/rec.cpp \ + cmd/src.cpp \ + libs/mapping/atlas.cpp \ + cmd/ana.cpp \ + view_image.cpp \ + manual_alignment.cpp \ + tracking/tract_report.cpp \ + cmd/exp.cpp \ + tracking/connectivity_matrix_dialog.cpp \ + libs/dsi/tessellated_icosahedron.cpp \ + cmd/atl.cpp \ + tracking/atlasdialog.cpp \ + cmd/cnt.cpp \ + cmd/vis.cpp \ + qcompletelineedit.cpp \ + libs/tracking/roi.cpp \ + libs/tracking/fib_data.cpp \ + libs/tracking/tracking_thread.cpp \ + cmd/ren.cpp \ + libs/mapping/connectometry_db.cpp \ + connectometry/createdbdialog.cpp \ + connectometry/match_db.cpp \ + connectometry/db_window.cpp \ + connectometry/group_connectometry.cpp \ + connectometry/group_connectometry_analysis.cpp \ + regtoolbox.cpp \ + cmd/cnn.cpp \ + cmd/qc.cpp \ + libs/dsi/basic_voxel.cpp \ + libs/dsi/image_model.cpp \ + cmd/reg.cpp \ + auto_track.cpp \ + cmd/atk.cpp \ + tracking/device.cpp \ + tracking/devicetablewidget.cpp \ + cmd/xnat.cpp \ + xnat_dialog.cpp \ + /home/matt/projects/trx_dsi_studio/DSI-Studio-TRX/trx-cpp/src/trx.cpp + +OTHER_FILES += \ + options.txt \ + dicom_tag.txt \ + FreeSurferColorLUT.txt + +DISTFILES += \ + LICENSE diff --git a/libs/tracking/tract_model.cpp b/libs/tracking/tract_model.cpp index 5f27763b8..b19c2f81c 100644 --- a/libs/tracking/tract_model.cpp +++ b/libs/tracking/tract_model.cpp @@ -9,8 +9,11 @@ #include #include #include +#include #include #include +#include +#include #include "roi.hpp" #include "tract_model.hpp" #include "fib_data.hpp" @@ -20,10 +23,60 @@ #include "tracking_method.hpp" #include "reg.hpp" #include +#include void prepare_idx(const std::string& file_name,std::shared_ptr in); void save_idx(const std::string& file_name,std::shared_ptr in); const tipl::rgb default_tract_color(255,160,60); +namespace +{ + tipl::matrix<4,4> lps_to_ras(const tipl::matrix<4,4>& lps) + { + tipl::matrix<4,4> ras(lps); + for(int row = 0; row < 2; ++row) + for(int col = 0; col < 4; ++col) + ras[row*4+col] = -ras[row*4+col]; + return ras; + } + + tipl::matrix<4,4> ras_to_lps(const tipl::matrix<4,4>& ras) + { + return lps_to_ras(ras); + } + + tipl::vector<3> voxel_size_from_affine(const tipl::matrix<4,4>& affine) + { + tipl::vector<3> vs; + for(int row = 0; row < 3; ++row) + { + tipl::vector<3> axis; + axis[0] = affine[row*4+0]; + axis[1] = affine[row*4+1]; + axis[2] = affine[row*4+2]; + vs[row] = float(axis.length()); + } + return vs; + } + + std::vector > affine_to_vector(const tipl::matrix<4,4>& affine) + { + std::vector > out(4,std::vector(4,0.0f)); + for(int i = 0; i < 4; ++i) + for(int j = 0; j < 4; ++j) + out[i][j] = affine[i*4+j]; + return out; + } + + std::string sanitize_trx_name(std::string name) + { + if(name.empty()) + return "group"; + for(auto& ch : name) + if(ch == '/' || ch == '\\' || ch == ' ') + ch = '_'; + return name; + } +} inline unsigned int get_cluster_color(const std::vector& tract_color) { return tract_color.empty() ? uint32_t(default_tract_color) : tract_color.front(); @@ -701,6 +754,118 @@ bool TractModel::load_tracts_from_file(const std::string& file_name,fib_data* ha if(!tck.load_from_file(file_name,loaded_tract_data)) return false; } + if (tipl::ends_with(file_name,"trx")) + { + try + { + std::unique_ptr > trx(trxmmap::load_from_zip(file_name)); + if(!trx.get()) + return false; + if(!trx->header.contains("DIMENSIONS") || !trx->header.contains("VOXEL_TO_RASMM")) + return false; + + const auto& dims = trx->header["DIMENSIONS"]; + if(dims.size() < 3) + return false; + geo[0] = dims[0]; + geo[1] = dims[1]; + geo[2] = dims[2]; + + tipl::matrix<4,4> ras_affine; + for(int i = 0;i < 4;++i) + for(int j = 0;j < 4;++j) + ras_affine[i*4+j] = trx->header["VOXEL_TO_RASMM"][i][j]; + source_trans_to_mni = ras_to_lps(ras_affine); + vs = voxel_size_from_affine(source_trans_to_mni); + + const auto& data = trx->streamlines->_data; + const auto& offsets = trx->streamlines->_offsets; + const auto offset_rows = offsets.rows(); + if(offset_rows == 0) + return false; + const size_t nb_streamlines = size_t(offset_rows-1); + loaded_tract_data.resize(nb_streamlines); + for(size_t s = 0; s < nb_streamlines; ++s) + { + const size_t start = size_t(offsets(int(s),0)); + const size_t end = size_t(offsets(int(s)+1,0)); + if(end < start || end > size_t(data.rows())) + return false; + auto& tract = loaded_tract_data[s]; + tract.resize((end-start)*3); + for(size_t i = start, pos = 0; i < end; ++i, pos += 3) + { + tract[pos] = float(data(int(i),0)); + tract[pos+1] = float(data(int(i),1)); + tract[pos+2] = float(data(int(i),2)); + } + } + + if(!trx->groups.empty()) + { + std::vector group_assignment(nb_streamlines, std::numeric_limits::max()); + tract_cluster_names.clear(); + bool has_overlap = false; + for(const auto& kv : trx->groups) + { + const unsigned int group_id = tract_cluster_names.size(); + tract_cluster_names.push_back(kv.first); + const auto& matrix = kv.second->_matrix; + for(int r = 0; r < matrix.rows(); ++r) + for(int c = 0; c < matrix.cols(); ++c) + { + const uint32_t index = matrix(r,c); + if(index >= nb_streamlines) + continue; + if(group_assignment[index] != std::numeric_limits::max() && + group_assignment[index] != group_id) + has_overlap = true; + else + group_assignment[index] = group_id; + } + } + bool all_assigned = std::all_of(group_assignment.begin(),group_assignment.end(), + [](unsigned int v){return v != std::numeric_limits::max();}); + if(!all_assigned) + { + const unsigned int ungrouped_id = tract_cluster_names.size(); + tract_cluster_names.push_back("ungrouped"); + for(auto& v : group_assignment) + if(v == std::numeric_limits::max()) + v = ungrouped_id; + all_assigned = true; + } + if(has_overlap) + tipl::out() << "TRX groups overlap; assigning streamlines to the first matching group."; + if(all_assigned) + { + loaded_tract_cluster.resize(nb_streamlines); + for(size_t i = 0; i < nb_streamlines; ++i) + loaded_tract_cluster[i] = group_assignment[i]; + } + } + + if(!trx->data_per_streamline.empty()) + { + for(const auto& kv : trx->data_per_streamline) + { + const auto& matrix = kv.second->_matrix; + if(matrix.rows() == int(nb_streamlines) && matrix.cols() == 1) + { + loaded_values.resize(nb_streamlines); + for(size_t i = 0; i < nb_streamlines; ++i) + loaded_values[i] = float(matrix(int(i),0)); + break; + } + } + } + } + catch(const std::exception& e) + { + tipl::out() << "TRX load failed: " << e.what(); + return false; + } + } if(loaded_tract_cluster.size() == loaded_tract_data.size()) @@ -709,7 +874,10 @@ bool TractModel::load_tracts_from_file(const std::string& file_name,fib_data* ha loaded_tract_cluster.swap(tract_cluster); } else + { tract_cluster.clear(); + tract_cluster_names.clear(); + } // handle trans_to_mni differences @@ -898,6 +1066,88 @@ bool TractModel::save_tracts_to_file(const std::string& file_name) tipl::out() << "save " << tract_data.size() << " tracts to " << file_name; tipl::out() << "dim:" << geo << " vs:" << vs; tipl::out() << "trans:" << trans_to_mni; + if(tipl::ends_with(file_name,"trx")) + { + try + { + const size_t nb_streamlines = tract_data.size(); + size_t nb_vertices = 0; + for(const auto& tract : tract_data) + nb_vertices += tract.size()/3; + if(nb_vertices > size_t(std::numeric_limits::max()) || + nb_streamlines > size_t(std::numeric_limits::max())) + { + tipl::out() << "TRX save failed: tract count exceeds TRX int limits."; + return false; + } + + trxmmap::TrxFile trx_file{int(nb_vertices), int(nb_streamlines)}; + trx_file.header["DIMENSIONS"] = {geo[0], geo[1], geo[2]}; + trx_file.header["NB_VERTICES"] = int(nb_vertices); + trx_file.header["NB_STREAMLINES"] = int(nb_streamlines); + trx_file.header["VOXEL_TO_RASMM"] = affine_to_vector(lps_to_ras(trans_to_mni)); + + size_t vertex_index = 0; + for(size_t s = 0; s < nb_streamlines; ++s) + { + const size_t n_points = tract_data[s].size()/3; + trx_file.streamlines->_offsets(int(s),0) = uint64_t(vertex_index); + trx_file.streamlines->_lengths(int(s)) = uint32_t(n_points); + for(size_t p = 0; p < n_points; ++p) + { + const size_t src = p*3; + trx_file.streamlines->_data(int(vertex_index+p),0) = half(tract_data[s][src]); + trx_file.streamlines->_data(int(vertex_index+p),1) = half(tract_data[s][src+1]); + trx_file.streamlines->_data(int(vertex_index+p),2) = half(tract_data[s][src+2]); + } + vertex_index += n_points; + } + if(nb_streamlines) + trx_file.streamlines->_offsets(int(nb_streamlines),0) = uint64_t(vertex_index); + + if(!tract_cluster.empty() && tract_cluster.size() == tract_data.size()) + { + std::map > clusters; + for(size_t i = 0; i < tract_cluster.size(); ++i) + clusters[tract_cluster[i]].push_back(uint32_t(i)); + for(const auto& kv : clusters) + { + std::string group_name = (kv.first < tract_cluster_names.size() && !tract_cluster_names[kv.first].empty()) ? + tract_cluster_names[kv.first] : std::string("cluster_") + std::to_string(kv.first); + group_name = sanitize_trx_name(group_name); + const auto& indices = kv.second; + auto group = new trxmmap::MMappedMatrix(); + std::tuple shape = std::make_tuple(int(indices.size()),1); + std::string filename = trx_file._uncompressed_folder_handle + "/group_" + group_name + ".uint32"; + group->mmap = trxmmap::_create_memmap(filename, shape, "w+", "uint32"); + new (&(group->_matrix)) Eigen::Map>(reinterpret_cast(group->mmap.data()), std::get<0>(shape), std::get<1>(shape)); + for(size_t i = 0; i < indices.size(); ++i) + group->_matrix(int(i),0) = indices[i]; + trx_file.groups[group_name] = group; + } + } + + if(!loaded_values.empty() && loaded_values.size() == tract_data.size()) + { + auto dps = new trxmmap::MMappedMatrix(); + std::tuple shape = std::make_tuple(int(nb_streamlines),1); + std::string filename = trx_file._uncompressed_folder_handle + "/dps_dsi_loaded_values.float16"; + dps->mmap = trxmmap::_create_memmap(filename, shape, "w+", "float16"); + new (&(dps->_matrix)) Eigen::Map>(reinterpret_cast(dps->mmap.data()), std::get<0>(shape), std::get<1>(shape)); + for(size_t i = 0; i < loaded_values.size(); ++i) + dps->_matrix(int(i),0) = half(loaded_values[i]); + trx_file.data_per_streamline["dsi_loaded_values"] = dps; + } + + trxmmap::save(trx_file,file_name,ZIP_CM_STORE); + return true; + } + catch(const std::exception& e) + { + tipl::out() << "TRX save failed: " << e.what(); + return false; + } + } if(tipl::ends_with(file_name,"tt.gz")) { return TinyTrack::save_to_file(file_name,geo,vs,trans_to_mni, @@ -1153,7 +1403,7 @@ std::string TractModel::get_obj(unsigned int& coordinate_count, //--------------------------------------------------------------------------- bool TractModel::save_all(const std::string& file_name, const std::vector >& all) -{ +{ if(all.empty()) return false; tipl::progress prog("save " + file_name); @@ -1195,6 +1445,32 @@ bool TractModel::save_all(const std::string& file_name, if(!result) return false; } + if (tipl::ends_with(file_name,".trx")) + { + std::vector > all_tract; + std::vector cluster; + std::vector cluster_names; + cluster_names.reserve(all.size()); + for(size_t cluster_index = 0; cluster_index < all.size(); ++cluster_index) + { + cluster_names.push_back(all[cluster_index]->name.empty() ? + std::string("cluster_") + std::to_string(cluster_index) : + all[cluster_index]->name); + auto& tract = all[cluster_index]->tract_data; + for (size_t j = 0;j < tract.size();++j) + { + all_tract.push_back(tract[j]); + cluster.push_back(cluster_index); + } + } + TractModel combined(*all[0]); + combined.clear(); + combined.add_tracts(all_tract); + combined.tract_cluster.swap(cluster); + combined.tract_cluster_names.swap(cluster_names); + if(!combined.save_tracts_to_file(file_name)) + return false; + } if (tipl::ends_with(file_name,".txt")) { std::ofstream out(file_name,std::ios::binary); @@ -1594,7 +1870,7 @@ bool TractModel::select_tracts(const std::vector& tracts_to_select } //--------------------------------------------------------------------------- bool TractModel::delete_repeated(float d) -{ +{ std::vector > x_reg; std::vector track_location1,track_location2; { diff --git a/libs/tracking/tract_model.hpp b/libs/tracking/tract_model.hpp index 8db2f7e7c..8f475887d 100644 --- a/libs/tracking/tract_model.hpp +++ b/libs/tracking/tract_model.hpp @@ -32,6 +32,7 @@ class TractModel{ // for loading multiple clusters // it can be empty std::vector tract_cluster; + std::vector tract_cluster_names; float loaded_value = 0.0f; std::vector loaded_values; public: @@ -93,6 +94,8 @@ class TractModel{ all_tracts.push_back(tract_model); return all_tracts; } + if(!tract_model->tract_cluster_names.empty()) + return separate_tracts(tract_model,tract_model->tract_cluster,tract_model->tract_cluster_names); std::ifstream in(std::string(file_name)+".txt"); return separate_tracts(tract_model,tract_model->tract_cluster, std::vector((std::istream_iterator(in)),(std::istream_iterator()))); @@ -195,7 +198,7 @@ class TractModel{ size_t get_deleted_track_count(void) const{return deleted_tract_data.size();} size_t get_visible_track_count(void) const{return tract_data.size();} - + auto get_tract_point(unsigned int index,unsigned int pos) const{return tipl::vector<3>(&tract_data[index][pos + (pos << 1)]);} const std::vector& get_tract(unsigned int index) const{return tract_data[index];} const std::vector >& get_tracts(void) const{return tract_data;} diff --git a/mainwindow.cpp b/mainwindow.cpp index 66dae31bd..c29e07334 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -395,7 +395,8 @@ void MainWindow::openFile(QStringList file_names) else if(QString(file_name).endsWith(".tt.gz") || QString(file_name).endsWith(".trk") || - QString(file_name).endsWith(".trk.gz")) + QString(file_name).endsWith(".trk.gz") || + QString(file_name).endsWith(".trx")) { auto file_list = QFileInfo(file_name).dir().entryList(QStringList("*fz"),QDir::Files|QDir::NoSymLinks); file_list << QFileInfo(file_name).dir().entryList(QStringList("*fib.gz"),QDir::Files|QDir::NoSymLinks); @@ -591,7 +592,8 @@ void MainWindow::loadFib(QString filename) } tracking_windows.back()->showNormal(); tracking_windows.back()->resize(1200,700); - if(filename.endsWith("trk.gz") || filename.endsWith("trk") || filename.endsWith("tck") || filename.endsWith("tt.gz")) + if(filename.endsWith("trk.gz") || filename.endsWith("trk") || filename.endsWith("tck") || + filename.endsWith("tt.gz") || filename.endsWith("trx")) { tracking_windows.back()->command({"open_tract",filename.toStdString()}); if(filename.endsWith("tck")) diff --git a/mainwindow.ui b/mainwindow.ui index 0f3ecf855..bc5289ac9 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -738,7 +738,7 @@ Varian (.fdf) QLabel { background-color: rgba(255, 255, 255, 157); color: black; } - Tractography (*.tt.gz) + Tractography (*.tt.gz *.trx) Qt::AlignCenter diff --git a/options.txt b/options.txt index c316babe9..d51efb959 100644 --- a/options.txt +++ b/options.txt @@ -40,7 +40,7 @@ Tracking_adv/Tracking Algorithm/tracking_method/Euler:RK4:Voxel tracking/0 Tracking_adv/Smoothing (1=random)/smoothing/float:-1.5:1:0.1:2/0 Tracking_adv/Check Ending/check_ending/Off:On/0 Tracking_adv/Default Otsu/otsu_threshold/float:0.1:1:0.1:2/0.6 -Tracking_adv/Output Format/track_format/tt.gz:trk.gz:txt/0 +Tracking_adv/Output Format/track_format/tt.gz:trk.gz:txt:trx/0 Rendering/Scale with voxel size/scale_voxel/Off:On/1 Rendering/Perspective/perspective/int/5 Rendering/3D Perspective/3d_perspective/float:0.5:3:0.5:1/1.0 diff --git a/regtoolbox.cpp b/regtoolbox.cpp index 1f3615855..a17b8047c 100644 --- a/regtoolbox.cpp +++ b/regtoolbox.cpp @@ -676,7 +676,7 @@ void RegToolBox::on_actionTemplate_Image_triggered() template void applyWarping(dual_reg& reg) { - QString filter = "Images (*.nii *nii.gz);;Tracts (*tt.gz);;All files (*)"; + QString filter = "Images (*.nii *nii.gz);;Tracts (*tt.gz *.trx);;All files (*)"; QStringList file_list = QFileDialog::getOpenFileNames(nullptr, subjectToTemplate ? "Open Subject Image" : "Open Template Image", QDir::currentPath(), filter); diff --git a/tracking/tracking_window.cpp b/tracking/tracking_window.cpp index a8e3fc6b5..d21b5ef38 100644 --- a/tracking/tracking_window.cpp +++ b/tracking/tracking_window.cpp @@ -106,7 +106,7 @@ bool command_history::get_filename(QWidget* parent,std::string& filename,const s (std::filesystem::path(default_parent_path)/ (post_fix[0] == '.' ? default_stem + post_fix : post_fix)).string())); if(tipl::ends_with(current_cmd,"_tracts") || tipl::ends_with(current_cmd,"_tract")) - filter = "Tract files (*.tt.gz *tt.gz *trk.gz *.trk);;MAT files (*.mat)"; + filter = "Tract files (*.tt.gz *tt.gz *trk.gz *.trk *.trx);;MAT files (*.mat)"; else if(tipl::ends_with(current_cmd,"_regions") || tipl::ends_with(current_cmd,"_region") || tipl::ends_with(current_cmd,"_volume")) @@ -313,7 +313,7 @@ tracking_window::tracking_window(QWidget *parent,std::shared_ptr new_h ui->perform_tracking->hide(); ui->stop_tracking->hide(); ui->enable_auto_tract->setText("Enable Tractography..."); - } + } } { tipl::out() << "prepare template and atlases" << std::endl; @@ -373,7 +373,7 @@ tracking_window::tracking_window(QWidget *parent,std::shared_ptr new_h set_data("fa_threshold",0.0f); set_data("step_size",0.0f); set_data("turning_angle",0.0f); - set_data("tube_diameter",0.15f); + set_data("tube_diameter",0.15f); } set_data("min_length",handle->min_length()); set_data("max_length",handle->max_length()); @@ -587,7 +587,7 @@ tracking_window::tracking_window(QWidget *parent,std::shared_ptr new_h connect(ui->actionPaint_Tracts,&QAction::triggered, this,[this](void){glWidget->setCursor(Qt::CrossCursor);glWidget->editing_option = GLWidget::selecting;tractWidget->edit_option = TractTableWidget::paint;}); connect(ui->actionMove_Objects,&QAction::triggered, this,[this](void){glWidget->setCursor(Qt::CrossCursor);glWidget->editing_option = GLWidget::moving;}); - } + } { connect(ui->actionRestore_window_layout,&QAction::triggered, this,[this](void){restoreGeometry(default_geo);restoreState(default_state);}); } @@ -1080,7 +1080,8 @@ void tracking_window::dropEvent(QDropEvent *event) for(auto each : droppedUrls) { auto file_name = each.toLocalFile(); - if(file_name.endsWith("tt.gz")) + if(file_name.endsWith("tt.gz") || file_name.endsWith("trx") || file_name.endsWith("trk") || + file_name.endsWith("trk.gz") || file_name.endsWith("tck")) tracts << file_name; if(file_name.endsWith("nii.gz") || file_name.endsWith("nii")) { diff --git a/tracking/tracking_window_action.cpp b/tracking/tracking_window_action.cpp index 69ea2ff2b..39167f161 100644 --- a/tracking/tracking_window_action.cpp +++ b/tracking/tracking_window_action.cpp @@ -323,7 +323,7 @@ bool tracking_window::command(std::vector cmd) return run->canceled(); slice_need_update = false; // turn off simple drawing - scene.paint_image(scene.view_image,false); + scene.paint_image(scene.view_image,false); if(!scene.view_image.save(cmd[1].c_str())) return run->failed("cannot save mapping to " + cmd[1]); return run->succeed(); @@ -410,6 +410,8 @@ bool tracking_window::command(std::vector cmd) tractWidget->command({"delete_all_tracts"});; for(const auto& each : tipl::search_files(cmd[1]+"/tracts","*tt.gz")) tractWidget->command({"open_tract",each}); + for(const auto& each : tipl::search_files(cmd[1]+"/tracts","*trx")) + tractWidget->command({"open_tract",each}); } prog(1,5); @@ -443,7 +445,7 @@ bool tracking_window::command(std::vector cmd) regionWidget->command({"open_region",each}); } - prog(4,5); + prog(4,5); for(const auto& line : tipl::read_text_file(cmd[1] + "/commands.csv")) command(tipl::split(line,',')); diff --git a/tracking/tract/tracttablewidget.cpp b/tracking/tract/tracttablewidget.cpp index 6fefd31de..3c1d1b1dd 100644 --- a/tracking/tract/tracttablewidget.cpp +++ b/tracking/tract/tracttablewidget.cpp @@ -283,7 +283,7 @@ void TractTableWidget::fetch_tracts(void) bool has_thread = false; for(unsigned int index = 0;index < thread_data.size();++index) if(thread_data[index].get()) - { + { { auto lock = tract_rendering[index]->start_writing(false); if(lock.get()) @@ -358,6 +358,8 @@ QString TractTableWidget::output_format(void) return ".trk.gz"; case 2: return ".txt"; + case 3: + return ".trx"; } return ""; } @@ -604,7 +606,7 @@ bool TractTableWidget::command(std::vector cmd) // allow for selecting multiple files auto file_list = QFileDialog::getOpenFileNames(this,QString::fromStdString(cmd[0]), QString::fromStdString(cur_tracking_window.history.file_stem()) + output_format(), - "Tract files (*tt.gz *.trk *trk.gz *.tck);;Text files (*.txt);;All files (*)"); + "Tract files (*tt.gz *.trk *trk.gz *.tck *.trx);;Text files (*.txt);;All files (*)"); if(file_list.isEmpty()) return run->canceled(); // allow sub command to be recorded