File: tck2connectome.cpp

package info (click to toggle)
mrtrix3 3.0.8-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 15,300 kB
  • sloc: cpp: 130,470; python: 9,603; sh: 597; makefile: 62; xml: 47
file content (224 lines) | stat: -rw-r--r-- 10,054 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/* Copyright (c) 2008-2025 the MRtrix3 contributors.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Covered Software is provided under this License on an "as is"
 * basis, without warranty of any kind, either expressed, implied, or
 * statutory, including, without limitation, warranties that the
 * Covered Software is free of defects, merchantable, fit for a
 * particular purpose or non-infringing.
 * See the Mozilla Public License v. 2.0 for more details.
 *
 * For more details, see http://www.mrtrix.org/.
 */

#include <set>

#include "command.h"
#include "image.h"
#include "thread_queue.h"
#include "types.h"

#include "dwi/tractography/file.h"
#include "dwi/tractography/properties.h"
#include "dwi/tractography/weights.h"
#include "dwi/tractography/mapping/loader.h"
#include "dwi/tractography/connectome/connectome.h"
#include "dwi/tractography/connectome/metric.h"
#include "dwi/tractography/connectome/mapper.h"
#include "dwi/tractography/connectome/matrix.h"
#include "dwi/tractography/connectome/tck2nodes.h"


using namespace MR;
using namespace App;
using namespace MR::DWI;
using namespace MR::DWI::Tractography;
using namespace MR::DWI::Tractography::Connectome;


void usage ()
{

  AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)";

  SYNOPSIS = "Generate a connectome matrix from a streamlines file and a node parcellation image";

  DESCRIPTION
  + MR::DWI::Tractography::Connectome::tck2nodes_description.c_str();

  EXAMPLES
  + Example ("Default usage",
             "tck2connectome tracks.tck nodes.mif connectome.csv -tck_weights_in weights.csv -out_assignments assignments.txt",
             "By default, the metric of connectivity quantified in the connectome matrix is the "
             "number of streamlines; or, if tcksift2 is used, the sum of streamline weights via the "
             "-tck_weights_in option. Use of the -out_assignments option is recommended as this "
             "enables subsequent use of the connectome2tck command.")

  + Example ("Generate a matrix consisting of the mean streamline length between each node pair",
             "tck2connectome tracks.tck nodes.mif distances.csv -scale_length -stat_edge mean",
             "By multiplying the contribution of each streamline to the connectome by the length "
             "of that streamline, and then, for each edge, computing the mean value across the "
             "contributing streamlines, one obtains a matrix where the value in each entry is the "
             "mean length across those streamlines belonging to that edge.")

  + Example ("Generate a connectome matrix where the value of connectivity is the \"mean FA\"",
             "tcksample tracks.tck FA.mif mean_FA_per_streamline.csv -stat_tck mean; "
             "tck2connectome tracks.tck nodes.mif mean_FA_connectome.csv -scale_file mean_FA_per_streamline.csv -stat_edge mean",
             "Here, a connectome matrix that is \"weighted by FA\" is generated in multiple steps: "
             "firstly, for each streamline, the value of the underlying FA image is sampled at each "
             "vertex, and the mean of these values is calculated to produce a single scalar value of "
             "\"mean FA\" per streamline; then, as each streamline is assigned to nodes within the "
             "connectome, the magnitude of the contribution of that streamline to the matrix is "
             "multiplied by the mean FA value calculated prior for that streamline; finally, for "
             "each connectome edge, across the values of \"mean FA\" that were contributed by all "
             "of the streamlines assigned to that particular edge, the mean value is calculated.")

  + Example ("Generate the connectivity fingerprint for streamlines seeded from a particular region",
             "tck2connectome fixed_seed_tracks.tck nodes.mif fingerprint.csv -vector",
             "This usage assumes that the streamlines being provided to the command have all been "
             "seeded from the (effectively) same location, and as such, only the endpoint of each "
             "streamline (not their starting point) is assigned based on the provided parcellation "
             "image. Accordingly, the output file contains only a vector of connectivity values "
             "rather than a matrix, since each streamline is assigned to only one node rather than two.");


  ARGUMENTS
  + Argument ("tracks_in",      "the input track file").type_tracks_in()
  + Argument ("nodes_in",       "the input node parcellation image").type_image_in()
  + Argument ("connectome_out", "the output .csv file containing edge weights").type_file_out();


  OPTIONS
  + MR::DWI::Tractography::Connectome::AssignmentOptions
  + MR::DWI::Tractography::Connectome::MetricOptions
  + MR::Connectome::MatrixOutputOptions

  + OptionGroup ("Other options for tck2connectome")

  + MR::DWI::Tractography::Connectome::EdgeStatisticOption

  + Tractography::TrackWeightsInOption

  + Option ("keep_unassigned", "By default, the program discards the information regarding those streamlines that are not successfully assigned to a node pair. "
                               "Set this option to keep these values (will be the first row/column in the output matrix)")

  + Option ("out_assignments", "output the node assignments of each streamline to a file; "
                               "this can be used subsequently e.g. by the command connectome2tck")
    + Argument ("path").type_file_out()

  + Option ("vector", "output a vector representing connectivities from a given seed point to target nodes, "
                      "rather than a matrix of node-node connectivities");

  REFERENCES
  + "If using the default \"radial search\" streamline-parcel assignment mechanism: " // Internal
    "Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. "
    "The effects of SIFT on the reproducibility and biological accuracy of the structural connectome. "
    "NeuroImage, 2015, 104, 253-265"

  + "If using -scale_invlength or -scale_invnodevol options: "
    "Hagmann, P.; Cammoun, L.; Gigandet, X.; Meuli, R.; Honey, C.; Wedeen, V. & Sporns, O. "
    "Mapping the Structural Core of Human Cerebral Cortex. "
    "PLoS Biology 6(7), e159";

}



template <typename T>
void execute (Image<node_t>& node_image, const node_t max_node_index, const std::set<node_t>& missing_nodes)
{
  // Are we generating a matrix or a vector?
  const bool vector_output = get_options ("vector").size();

  // Do we need to keep track of the nodes to which each streamline is
  //   assigned, or would it be a waste of memory?
  const bool track_assignments = get_options ("out_assignments").size();

  // Get the metric, assignment mechanism & per-edge statistic for connectome construction
  Metric metric;
  Tractography::Connectome::setup_metric (metric, node_image);
  std::unique_ptr<Tck2nodes_base> tck2nodes (load_assignment_mode (node_image));
  auto opt = get_options ("stat_edge");
  const stat_edge statistic = opt.size() ? stat_edge(int(opt[0][0])) : stat_edge::SUM;

  // Prepare for reading the track data
  Tractography::Properties properties;
  Tractography::Reader<float> reader (argument[0], properties);

  // Initialise classes in preparation for multi-threading
  Mapping::TrackLoader loader (reader, properties["count"].empty() ? 0 : to<size_t>(properties["count"]), "Constructing connectome");
  Tractography::Connectome::Mapper mapper (*tck2nodes, metric);
  Tractography::Connectome::Matrix<T> connectome (max_node_index, statistic, vector_output, track_assignments);

  // Multi-threaded connectome construction
  if (tck2nodes->provides_pair()) {
    Thread::run_queue (
        loader,
        Thread::batch (Tractography::Streamline<float>()),
        Thread::multi (mapper),
        Thread::batch (Mapped_track_nodepair()),
        connectome);
  } else {
    Thread::run_queue (
        loader,
        Thread::batch (Tractography::Streamline<float>()),
        Thread::multi (mapper),
        Thread::batch (Mapped_track_nodelist()),
        connectome);
  }

  connectome.finalize();
  connectome.error_check (missing_nodes);

  connectome.save (argument[2], get_options ("keep_unassigned").size(), get_options ("symmetric").size(), get_options ("zero_diagonal").size());

  opt = get_options ("out_assignments");
  if (opt.size())
    connectome.write_assignments (opt[0][0]);
}



void run ()
{
  auto node_header = Header::open (argument[1]);
  MR::Connectome::check (node_header);
  auto node_image = node_header.get_image<node_t>();

  // First, find out how many segmented nodes there are, so the matrix can be pre-allocated
  // Also check for node volume for all nodes
  vector<uint32_t> node_volumes (1, 0);
  node_t max_node_index = 0;
  for (auto i = Loop (node_image, 0, 3) (node_image); i; ++i) {
    if (node_image.value() > max_node_index) {
      max_node_index = node_image.value();
      node_volumes.resize (max_node_index + 1, 0);
    }
    ++node_volumes[node_image.value()];
  }

  std::set<node_t> missing_nodes;
  for (size_t i = 1; i != node_volumes.size(); ++i) {
    if (!node_volumes[i])
      missing_nodes.insert (i);
  }
  if (missing_nodes.size()) {
    WARN ("The following nodes are missing from the parcellation image:");
    std::set<node_t>::iterator i = missing_nodes.begin();
    std::string list = str(*i);
    for (++i; i != missing_nodes.end(); ++i)
      list += ", " + str(*i);
    WARN (list);
    WARN ("(This may indicate poor parcellation image preparation, use of incorrect or incomplete LUT file(s) in labelconvert, or very poor registration)");
  }

  if (max_node_index >= node_count_ram_limit) {
    INFO ("Very large number of nodes detected; using single-precision floating-point storage");
    execute<float> (node_image, max_node_index, missing_nodes);
  } else {
    execute<double> (node_image, max_node_index, missing_nodes);
  }
}