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 225 226 227 228 229 230
|
/* 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 <string>
#include "command.h"
#include "exception.h"
#include "mrtrix.h"
#include "ordered_thread_queue.h"
#include "types.h"
#include "dwi/tractography/file.h"
#include "dwi/tractography/properties.h"
#include "dwi/tractography/roi.h"
#include "dwi/tractography/weights.h"
#include "dwi/tractography/editing/editing.h"
#include "dwi/tractography/editing/loader.h"
#include "dwi/tractography/editing/receiver.h"
#include "dwi/tractography/editing/worker.h"
using namespace MR;
using namespace App;
using namespace MR::DWI;
using namespace MR::DWI::Tractography;
using namespace MR::DWI::Tractography::Editing;
void usage ()
{
AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)";
SYNOPSIS = "Perform various editing operations on track files";
DESCRIPTION
+ "This command can be used to perform various types of manipulations "
"on track data. A range of such manipulations are demonstrated in the "
"examples provided below.";
EXAMPLES
+ Example ("Concatenate data from multiple track files into one",
"tckedit *.tck all_tracks.tck",
"Here the wildcard operator is used to select all files in the "
"current working directory that have the .tck filetype suffix; but "
"input files can equivalently be specified one at a time explicitly.")
+ Example ("Extract a reduced number of streamlines",
"tckedit in_many.tck out_few.tck -number 1k -skip 500",
"The number of streamlines requested would typically be less "
"than the number of streamlines in the input track file(s); if it "
"is instead greater, then the command will issue a warning upon "
"completion. By default the streamlines for the output file are "
"extracted from the start of the input file(s); in this example the "
"command is instead instructed to skip the first 500 streamlines, and "
"write to the output file streamlines 501-1500.")
+ Example ("Extract streamlines based on selection criteria",
"tckedit in.tck out.tck -include ROI1.mif -include ROI2.mif -minlength 25",
"Multiple criteria can be added in a single invocation of tckedit, "
"and a streamline must satisfy all criteria imposed in order to be "
"written to the output file. Note that both -include and -exclude "
"options can be specified multiple times to provide multiple "
"waypoints / exclusion masks.")
+ Example ("Select only those streamline vertices within a mask",
"tckedit in.tck cropped.tck -mask mask.mif",
"The -mask option is applied to each streamline vertex independently, "
"rather than to each streamline, retaining only those streamline vertices "
"within the mask. As such, use of this option may result in a greater "
"number of output streamlines than input streamlines, as a single input "
"streamline may have the vertices at either endpoint retained but some "
"vertices at its midpoint removed, effectively cutting one long streamline "
"into multiple shorter streamlines.");
ARGUMENTS
+ Argument ("tracks_in", "the input track file(s)").type_tracks_in().allow_multiple()
+ Argument ("tracks_out", "the output track file").type_tracks_out();
OPTIONS
+ ROIOption
+ LengthOption
+ TruncateOption
+ WeightsOption
+ OptionGroup ("Other options specific to tckedit")
+ Option ("inverse", "output the inverse selection of streamlines based on the criteria provided; "
"i.e. only those streamlines that fail at least one selection criterion, "
"and/or vertices that are outside masks if provided, will be written to file")
+ Option ("ends_only", "only test the ends of each streamline against the provided include/exclude ROIs")
// TODO Input weights with multiple input files currently not supported
+ OptionGroup ("Options for handling streamline weights")
+ Tractography::TrackWeightsInOption
+ Tractography::TrackWeightsOutOption;
// TODO Additional options?
// - Peak curvature threshold
// - Mean curvature threshold
}
void erase_if_present (Tractography::Properties& p, const std::string s)
{
auto i = p.find (s);
if (i != p.end())
p.erase (i);
}
void run ()
{
const size_t num_inputs = argument.size() - 1;
const std::string output_path = argument[num_inputs];
// Make sure configuration is sensible
if (get_options("tck_weights_in").size() && num_inputs > 1)
throw Exception ("Cannot use per-streamline weighting with multiple input files");
// Get the consensus streamline properties from among the multiple input files
Tractography::Properties properties;
size_t count = 0;
vector<std::string> input_file_list;
for (size_t file_index = 0; file_index != num_inputs; ++file_index) {
input_file_list.push_back (argument[file_index]);
Properties p;
Reader<float> (argument[file_index], p);
for (const auto& i : p.comments) {
bool present = false;
for (const auto& j: properties.comments)
if ( (present = (i == j)) )
break;
if (!present)
properties.comments.push_back (i);
}
for (const auto& i : p.prior_rois) {
const auto potential_matches = properties.prior_rois.equal_range (i.first);
bool present = false;
for (auto j = potential_matches.first; !present && j != potential_matches.second; ++j)
present = (i.second == j->second);
if (!present)
properties.prior_rois.insert (i);
}
size_t this_count = 0;
for (const auto& i : p) {
if (i.first == "count") {
this_count = to<float> (i.second);
} else {
auto existing = properties.find (i.first);
if (existing == properties.end())
properties.insert (i);
else if (i.second != existing->second)
existing->second = "variable";
}
}
count += this_count;
}
DEBUG ("estimated number of input tracks: " + str(count));
// Remove keyval "total_count", as there is ambiguity about what _should_ be
// contained in that field upon editing one or more existing tractograms
// (it has a specific interpretation in the context of streamline generation only)
erase_if_present (properties, "total_count");
load_rois (properties);
properties.compare_stepsize_rois();
// Some properties from tracking may be overwritten by this editing process
// Due to the potential use of masking, we have no choice but to clear the
// properties class of any fields that would otherwise propagate through
// and be applied as part of this editing
erase_if_present (properties, "min_dist");
erase_if_present (properties, "max_dist");
erase_if_present (properties, "min_weight");
erase_if_present (properties, "max_weight");
Editing::load_properties (properties);
// Parameters that the worker threads need to be aware of, but do not appear in Properties
const bool inverse = get_options ("inverse").size();
const bool ends_only = get_options ("ends_only").size();
// Parameters that the output thread needs to be aware of
const size_t number = get_option_value ("number", size_t(0));
const size_t skip = get_option_value ("skip", size_t(0));
Loader loader (input_file_list);
Worker worker (properties, inverse, ends_only);
Receiver receiver (output_path, properties, number, skip);
Thread::run_ordered_queue (
loader,
Thread::batch (Streamline<>()),
Thread::multi (worker),
Thread::batch (Streamline<>()),
receiver);
}
|