File: mredit.cpp

package info (click to toggle)
mrtrix3 3.0~rc3%2Bgit135-g2b8e7d0c2-3
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 34,248 kB
  • sloc: cpp: 117,101; python: 6,472; sh: 638; makefile: 226; xml: 39; ansic: 20
file content (218 lines) | stat: -rw-r--r-- 7,565 bytes parent folder | download | duplicates (2)
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
/*
 * Copyright (c) 2008-2018 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/
 *
 * MRtrix3 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * For more details, see http://www.mrtrix.org/
 */


#include <set>

#include "command.h"
#include "image.h"
#include "image_helpers.h"
#include "transform.h"
#include "types.h"

#include "algo/copy.h"

using namespace MR;
using namespace App;


// TODO:
// * Operate on mask images rather than arbitrary images?
// * Remove capability to edit in-place - just deal with image swapping in the script?
// * Tests


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

  SYNOPSIS = "Directly edit the intensities within an image from the command-line";

  DESCRIPTION
  + "A range of options are provided to enable direct editing of "
    "voxel intensities based on voxel / real-space coordinates. "

    "If only one image path is provided, the image will be edited in-place "
    "(use at own risk); if input and output image paths are provided, the "
    "output will contain the edited image, and the original image will not "
    "be modified in any way.";

  ARGUMENTS
  + Argument ("input", "the input image").type_image_in()
  + Argument ("output", "the (optional) output image").type_image_out().optional();

  OPTIONS
  + Option ("plane", "fill one or more planes on a particular image axis").allow_multiple()
    + Argument ("axis").type_integer (0, 2)
    + Argument ("coord").type_sequence_int()
    + Argument ("value").type_float()

  + Option ("sphere", "draw a sphere with radius in mm").allow_multiple()
    + Argument ("position").type_sequence_float()
    + Argument ("radius").type_float()
    + Argument ("value").type_float()

  + Option ("voxel", "change the image value within a single voxel").allow_multiple()
    + Argument ("position").type_sequence_float()
    + Argument ("value").type_float()

  + Option ("scanner", "indicate that coordinates are specified in scanner space, rather than as voxel coordinates");

}



class Vox : public Eigen::Array3i
{ MEMALIGN (Vox)
  public:
    using Eigen::Array3i::Array3i;
    Vox (const Eigen::Vector3& p) :
        Eigen::Array3i { int(std::round (p[0])), int(std::round (p[1])), int(std::round (p[2])) } { }
    bool operator< (const Vox& i) const {
      return (i[0] == (*this)[0] ? (i[1] == (*this)[1] ? (i[2] < (*this)[2]) : (i[1] < (*this)[1])) : (i[0] < (*this)[0]));
    }
};



const Vox voxel_offsets[6] = { { 0,  0, -1},
                               { 0,  0,  1},
                               { 0, -1,  0},
                               { 0,  1,  0},
                               {-1,  0,  0},
                               { 1,  0,  0} };



void run ()
{
  bool inplace = (argument.size() == 1);
  auto H = Header::open (argument[0]);
  auto in = H.get_image<float> (inplace); // Need to set read/write flag
  Image<float> out;
  if (inplace) {
    out = Image<float> (in);
  } else {
    if (std::string(argument[1]) == std::string(argument[0])) // Not ideal test - could be different paths to the same file
      throw Exception ("Do not provide same image as input and output; instad specify image to be edited in-place");
    out = Image<float>::create (argument[1], H);
    copy (in, out);
  }

  Transform transform (H);
  const bool scanner = get_options ("scanner").size();
  if (scanner && H.ndim() < 3)
    throw Exception ("Cannot specify scanner-space coordinates if image has less than 3 dimensions");

  size_t operation_count = 0;

  auto opt = get_options ("plane");
  if (opt.size()) {
    if (H.ndim() != 3)
      throw Exception ("-plane option only works for 3D images");
    if (scanner)
      throw Exception ("-plane option cannot be used with scanner-space coordinates");
  }
  operation_count += opt.size();
  for (auto p : opt) {
    const size_t axis = p[0];
    const auto coords = parse_ints (p[1]);
    const float value = p[2];
    const std::array<size_t, 2> loop_axes { { axis == 0 ? size_t(1) : size_t(0), axis == 2 ? size_t(1) : size_t(2) } };
    for (auto c : coords) {
      out.index (axis) = c;
      for (auto outer = Loop(loop_axes[0]) (out); outer; ++outer) {
        for (auto inner = Loop(loop_axes[1]) (out); inner; ++inner)
          out.value() = value;
      }
    }
  }

  opt = get_options ("sphere");
  if (opt.size() && H.ndim() != 3)
    throw Exception ("-sphere option only works for 3D images");
  operation_count += opt.size();
  for (auto s : opt) {
    const auto position = parse_floats (s[0]);
    Eigen::Vector3 centre_scannerspace (position[0], position[1], position[2]);
    const default_type radius = s[1];
    const float value = s[2];
    if (position.size() != 3)
      throw Exception ("Centre of sphere must be defined using 3 comma-separated values");
    Eigen::Vector3 centre_voxelspace (centre_scannerspace);
    if (scanner)
      centre_voxelspace = transform.scanner2voxel * centre_scannerspace;
    else
      centre_scannerspace = transform.voxel2scanner * centre_voxelspace;
    std::set<Vox> processed;
    vector<Vox> to_expand;
    const Vox seed_voxel (centre_voxelspace);
    processed.insert (seed_voxel);
    to_expand.push_back (seed_voxel);
    while (to_expand.size()) {
      const Vox v (to_expand.back());
      to_expand.pop_back();
      const Eigen::Vector3 v_scanner = transform.voxel2scanner * v.matrix().cast<default_type>();
      const default_type distance = (v_scanner - centre_scannerspace).norm();
      if (distance < radius) {
        if (!is_out_of_bounds (H, v)) {
          assign_pos_of (v).to (out);
          out.value() = value;
        }
        for (size_t i = 0; i != 6; ++i) {
          const Vox v_adj (v + voxel_offsets[i]);
          if (processed.find (v_adj) == processed.end()) {
            processed.insert (v_adj);
            to_expand.push_back (v_adj);
          }
        }
      }
    }
  }

  opt = get_options ("voxel");
  operation_count += opt.size();
  for (auto v : opt) {
    const auto position = parse_floats (v[0]);
    const float value = v[1];
    if (position.size() != H.ndim())
      throw Exception ("Image has " + str(H.ndim()) + " dimensions, but -voxel option position " + std::string(v[0]) + " provides only " + str(position.size()) + " coordinates");
    if (scanner) {
      Eigen::Vector3 p (position[0], position[1], position[2]);
      p = transform.scanner2voxel * p;
      const Vox voxel (p);
      assign_pos_of (voxel).to (out);
      for (size_t axis = 3; axis != out.ndim(); ++axis) {
        if (std::round (position[axis]) != position[axis])
          throw Exception ("Non-spatial coordinates provided using -voxel option must be provided as integers");
        out.index(axis) = position[axis];
      }
    } else {
      for (size_t axis = 0; axis != out.ndim(); ++axis) {
        if (std::round (position[axis]) != position[axis])
          throw Exception ("Voxel coordinates provided using -voxel option must be provided as integers");
        out.index(axis) = position[axis];
      }
    }
    out.value() = value;
  }

  if (!operation_count) {
    if (inplace) {
      WARN ("No edits specified; image will be unaffected");
    } else {
      WARN ("No edits specified; output image will be copy of input");
    }
  }
}