|
26 | 26 | #include "types.h" |
27 | 27 |
|
28 | 28 | #include "connectome/connectome.h" |
| 29 | +#include "connectome/lut.h" |
29 | 30 | #include "connectome/mat2vec.h" |
30 | 31 | #include "dwi/tractography/connectome/connectome.h" |
31 | 32 | #include "dwi/tractography/connectome/mapped_track.h" |
@@ -87,6 +88,13 @@ void usage() { |
87 | 88 | "the prefix (and trailing underscore) is stripped from node names in the output") |
88 | 89 | + Argument ("prefix").type_text() |
89 | 90 |
|
| 91 | + + Option ("lut", "lookup table mapping node names to numeric indices " |
| 92 | + "(supports FreeSurfer, AAL, ITK-SNAP, and MRtrix LUT formats); " |
| 93 | + "when provided, the output matrix rows and columns are ordered by node index " |
| 94 | + "rather than alphabetically, matching tck2connectome output ordering. " |
| 95 | + "Requires -group_prefix") |
| 96 | + + Argument ("path").type_file_in() |
| 97 | + |
90 | 98 | + MR::DWI::Tractography::TrackWeightsInOption |
91 | 99 |
|
92 | 100 | + MR::DWI::Tractography::Connectome::EdgeStatisticOption |
@@ -122,23 +130,45 @@ void execute(const node_t max_node_index, |
122 | 130 | for (size_t i = 0; i < streamline_groups.size(); ++i) { |
123 | 131 | const auto &grps = streamline_groups[i]; |
124 | 132 | const float w = weights.empty() ? 1.0f : weights[i]; |
125 | | - // Each streamline's group list contains one entry per endpoint assignment. |
126 | | - // Deduplicate and take the first two distinct nodes as the edge endpoints; |
127 | | - // using Mapped_track_nodepair ensures exactly one edge per streamline |
128 | | - // (Mapped_track_nodelist would add spurious self-connections). |
| 133 | + // Deduplicate node memberships for this streamline. |
| 134 | + // For a single-atlas run, a streamline has at most 2 unique nodes (one per |
| 135 | + // endpoint), producing one edge — identical to tck2connectome behaviour. |
| 136 | + // For a combined-atlas run, a streamline may have 4 unique nodes (2 per atlas), |
| 137 | + // so we emit one edge per unique pair to populate all atlas-block sub-matrices. |
129 | 138 | std::vector<node_t> unique_grps; |
130 | 139 | for (const auto n : grps) { |
131 | 140 | if (std::find(unique_grps.begin(), unique_grps.end(), n) == unique_grps.end()) |
132 | 141 | unique_grps.push_back(n); |
133 | 142 | } |
134 | | - const node_t n1 = unique_grps.size() >= 1 ? unique_grps[0] : node_t(0); |
135 | | - const node_t n2 = unique_grps.size() >= 2 ? unique_grps[1] : node_t(0); |
136 | | - Mapped_track_nodepair mapped; |
137 | | - mapped.set_track_index(i); |
138 | | - mapped.set_factor(1.0f); |
139 | | - mapped.set_weight(w); |
140 | | - mapped.set_nodes(NodePair(n1, n2)); |
141 | | - connectome(mapped); |
| 143 | + if (unique_grps.empty()) { |
| 144 | + // Unassigned streamline — record as (0, 0) |
| 145 | + Mapped_track_nodepair mapped; |
| 146 | + mapped.set_track_index(i); |
| 147 | + mapped.set_factor(1.0f); |
| 148 | + mapped.set_weight(w); |
| 149 | + mapped.set_nodes(NodePair(node_t(0), node_t(0))); |
| 150 | + connectome(mapped); |
| 151 | + } else if (unique_grps.size() == 1) { |
| 152 | + // One endpoint assigned — record as (node, 0) |
| 153 | + Mapped_track_nodepair mapped; |
| 154 | + mapped.set_track_index(i); |
| 155 | + mapped.set_factor(1.0f); |
| 156 | + mapped.set_weight(w); |
| 157 | + mapped.set_nodes(NodePair(unique_grps[0], node_t(0))); |
| 158 | + connectome(mapped); |
| 159 | + } else { |
| 160 | + // Two or more unique nodes: emit one edge per unique pair. |
| 161 | + for (size_t j = 0; j < unique_grps.size(); ++j) { |
| 162 | + for (size_t k = j + 1; k < unique_grps.size(); ++k) { |
| 163 | + Mapped_track_nodepair mapped; |
| 164 | + mapped.set_track_index(i); |
| 165 | + mapped.set_factor(1.0f); |
| 166 | + mapped.set_weight(w); |
| 167 | + mapped.set_nodes(NodePair(unique_grps[j], unique_grps[k])); |
| 168 | + connectome(mapped); |
| 169 | + } |
| 170 | + } |
| 171 | + } |
142 | 172 | ++progress; |
143 | 173 | } |
144 | 174 | } |
@@ -178,12 +208,24 @@ void run() { |
178 | 208 | group_prefix = std::string(opt[0][0]) + "_"; |
179 | 209 | } |
180 | 210 |
|
| 211 | + // Load LUT if provided (requires -group_prefix) |
| 212 | + std::unique_ptr<MR::Connectome::LUT> lut; |
| 213 | + { |
| 214 | + auto opt = get_options("lut"); |
| 215 | + if (!opt.empty()) { |
| 216 | + if (group_prefix.empty()) |
| 217 | + throw Exception("-lut requires -group_prefix to identify which groups to match against the lookup table"); |
| 218 | + lut = std::make_unique<MR::Connectome::LUT>(std::string(opt[0][0])); |
| 219 | + } |
| 220 | + } |
| 221 | + |
181 | 222 | std::vector<std::string> group_names = collect_group_names(*trx, group_prefix); |
182 | 223 |
|
183 | 224 | if (group_names.empty()) |
184 | 225 | throw Exception("No groups match the specified prefix '" + group_prefix + "'; check the group names with tckinfo"); |
185 | 226 |
|
186 | | - GroupNodeMapping mapping = build_group_node_mapping(group_names, group_prefix); |
| 227 | + GroupNodeMapping mapping = lut ? build_group_node_mapping(group_names, group_prefix, *lut) |
| 228 | + : build_group_node_mapping(group_names, group_prefix); |
187 | 229 | const node_t max_node_index = static_cast<node_t>(mapping.max_node_index); |
188 | 230 |
|
189 | 231 | const auto streamline_groups_u32 = invert_group_memberships(*trx, mapping.group_to_node); |
|
0 commit comments