RegistrationEnergyParser.h
1 /*
2  * Medical Image Registration ToolKit (MIRTK)
3  *
4  * Copyright 2013-2017 Imperial College London
5  * Copyright 2013-2017 Andreas Schuh
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 
20 #include "mirtk/String.h"
21 #include "mirtk/Stream.h"
22 #include "mirtk/Array.h"
23 
24 
25 namespace mirtk {
26 
27 
28 /**
29  * GRAMMAR (not up to date and complete)
30  *
31  * energy:
32  * similarities
33  * similarities + constraints
34  *
35  * similarities:
36  * weighted_similarity + similarities
37  * weighted_similarity
38  *
39  * weighted_similiarity:
40  * weight * similarity
41  * weight similarity
42  * similarity
43  *
44  * similarity:
45  * NAME(NAME, NAME)
46  * NAME[NAME](NAME, NAME)
47  *
48  * constraints:
49  * weighted_constraint + constraints
50  * weighted_constraint
51  *
52  * weighted_constraint:
53  * weight * constraint
54  * weight constraint
55  * constraint
56  *
57  * constraint:
58  * NAME
59  * NAME(T)
60  * NAME[NAME]
61  * NAME[NAME](T)
62  *
63  * weight:
64  * NUMBER
65  * NUMBER/NUMBER
66  *
67  */
69 {
71 
77 
78  // ---------------------------------------------------------------------------
79  /// Enumeration of input data types
80  enum DataType
81  {
82  IMAGE,
83  POLYDATA
84  };
85 
86  // ---------------------------------------------------------------------------
87  /// Enumeration of terminal symbols
88  enum TokenEnum
89  {
90  NAME,
91  INTEGER,
92  NUMBER,
93  END,
94  SPACE = ' ',
95  PLUS = '+',
96  MINUS = '-',
97  MUL = '*',
98  DIV = '/',
99  LP = '(',
100  RP = ')',
101  LB = '[',
102  RB = ']',
103  LCB = '{',
104  RCB = '}',
105  COMMA = ',',
106  COLON = ':',
107  CIRC = 'o', ///< Function composition
108  CARET = '^' ///< Exponentiation of transformation
109  };
110 
111  // ---------------------------------------------------------------------------
112  /// Token of energy function description
113  struct Token
114  {
115  TokenEnum _Token;
116  double _Number;
117  int _Integer;
118  string _Name;
119 
120  Token(TokenEnum token) : _Token(token) {}
121  Token(char c) : _Token(TokenEnum(c)) {}
122  Token(double number) : _Token(NUMBER), _Number(number),
123  _Integer(static_cast<int>(number)) {}
124  Token(string name) : _Token(NAME), _Name(name) {}
125 
126  Token(const Token &token)
127  :
128  _Token (token._Token),
129  _Number (token._Number),
130  _Integer(token._Integer),
131  _Name (token._Name)
132  {}
133 
134  Token &operator =(const Token &token)
135  {
136  _Token = token._Token;
137  _Number = token._Number;
138  _Integer = token._Integer;
139  _Name = token._Name;
140  return *this;
141  }
142 
143  bool operator ==(TokenEnum token) const { return _Token == token; }
144  bool operator !=(TokenEnum token) const { return _Token != token; }
145  };
146 
147  // ---------------------------------------------------------------------------
148  /// Get next token of energy function description
149  Token NextToken(istream &in, bool skip_ws = true)
150  {
151  char c;
152 
153  // Skip whitespaces and detect end of input
154  if (skip_ws) {
155  do {
156  if (!in.get(c)) return END;
157  } while (::isspace(c));
158  } else {
159  if (!in.get(c)) return END;
160  }
161 
162  // Get next token
163  switch (c) {
164  case ' ':
165  case '*':
166  case '/':
167  case '-':
168  case '+':
169  case '(':
170  case ')':
171  case '[':
172  case ']':
173  case ',':
174  case ':':
175  case '^':
176  case '{':
177  case '}':
178  return c;
179  case '0': case '1': case '2': case '3': case '4':
180  case '5': case '6': case '7': case '8': case '9':
181  case '.': {
182  string number(1, c);
183  bool before_decimal_point = (c != '.');
184  while (in.get(c)) {
185  if (isdigit(c) || (c == '.' && before_decimal_point)) {
186  if (c == '.') before_decimal_point = false;
187  number += c;
188  } else if (c == 'e' || c == 'E') {
189  bool is_next_token = false;
190  string exponent(1, c);
191  if (in.get(c)) {
192  exponent += c;
193  if (c == '-' || c == '+') {
194  if (in.get(c)) {
195  exponent += c;
196  if (!isdigit(c)) {
197  is_next_token = true;
198  }
199  } else {
200  is_next_token = true;
201  }
202  } else if (!isdigit(c)) {
203  is_next_token = true;
204  }
205  if (!is_next_token) {
206  while (in.get(c)) {
207  if (isdigit(c)) {
208  exponent += c;
209  } else {
210  in.putback(c);
211  break;
212  }
213  }
214  }
215  } else {
216  is_next_token = true;
217  }
218  if (is_next_token) {
219  for (auto it = exponent.rbegin(); it != exponent.rend(); ++it) {
220  in.putback(*it);
221  }
222  } else {
223  number += exponent;
224  }
225  break;
226  } else {
227  in.putback(c);
228  break;
229  }
230  }
231  double value = .0;
232  if (!FromString(number, value)) {
233  in.setstate(std::ios::failbit);
234  }
235  return value;
236  }
237  case 'o': {
238  char c2;
239  if (!in.get(c2)) return c;
240  in.putback(c2);
241  if (!::isalnum(c2) && c2 != '_') return c;
242  string name(1, c);
243  while (in.get(c) && (isalnum(c) || c == '_')) name += c;
244  in.putback(c);
245  return name;
246  }
247  default: {
248  if (::isalpha(c)) {
249  string name(1, c);
250  while (in.get(c) && (isalnum(c) || c == '_')) name += c;
251  in.putback(c);
252  return name;
253  }
254  cout << endl;
255  cerr << "Invalid character in objective function description: \"" << c
256  << "\" (ASCII code " << int(c) << ")" << endl;
257  exit(1);
258  return END;
259  }
260  }
261  }
262 
263  // ---------------------------------------------------------------------------
264  /// Evaluate weight of energy term
265  double Number(istream &in, Token &token)
266  {
267  double w = 1.0;
268  while (token == MINUS || token == PLUS) {
269  if (token == MINUS) w *= -1.0;
270  token = NextToken(in);
271  }
272  if (token == NUMBER) {
273  w *= token._Number;
274  token = NextToken(in);
275  if (token != DIV) return w;
276  token = NextToken(in);
277  if (token != NUMBER) {
278  cout << endl;
279  cerr << "Expected number after / in energy formula" << endl;
280  exit(1);
281  }
282  double d = token._Number;
283  if (d == .0) {
284  cout << endl;
285  cerr << "Division by zero in energy formula!" << endl;
286  exit(1);
287  }
288  token = NextToken(in);
289  return w / d;
290  }
291  return w;
292  }
293 
294  // ---------------------------------------------------------------------------
295  /// Whether a given number is an integer value
296  bool IsInteger(double number)
297  {
298  return (static_cast<double>(static_cast<int>(number)) == number);
299  }
300 
301  // ---------------------------------------------------------------------------
302  /// Parse input identifier with MATLAB style indexing syntax
303  Array<int> InputIndex(istream &in, Token &token, const string &name,
304  DataType type, int max_index)
305  {
306  Array<int> index;
307  const char *c;
308  const char *type_name;
309  const char *id_name;
310 
311  switch (type) {
312  case IMAGE:
313  c = "I"; // as in "Image"
314  type_name = "images";
315  id_name = "image identifier";
316  break;
317  case POLYDATA:
318  c = "PCS"; // as in "Point set", "Curve", "Surface"
319  type_name = "point set";
320  id_name = "point set identifier";
321  break;
322  default:
323  cerr << "RegistrationEnergyParser::InputIndex: Invalid data type identifier: " << type << endl;
324  exit(1);
325  }
326 
327  if (token != NAME || token._Name.substr(0, 1).find_first_of(c) != 0) return index;
328 
329  if (token._Name.size() == 1) {
330  token = NextToken(in);
331  if (token != LP) {
332  cout << endl;
333  cerr << "Expected single index or ( after input identifier of similarity term: " << name << endl;
334  exit(1);
335  }
336  token = NextToken(in);
337  bool inside_square_brackets = (token == LB);
338  if (inside_square_brackets) token = NextToken(in);
339  do {
340  int start = 1;
341  if (token == NAME && token._Name == "end") {
342  start = -1;
343  token = NextToken(in);
344  if (token == MINUS) {
345  token = NextToken(in);
346  if (token != NUMBER || !IsInteger(token._Number) || token._Integer < 0) {
347  cout << endl;
348  cerr << "Expected positive integer after 'end-' in input identifier of similarity term: " << name << endl;
349  exit(1);
350  }
351  start = -token._Integer;
352  token = NextToken(in);
353  }
354  } else if (token == NUMBER && IsInteger(token._Number)) {
355  start = token._Integer;
356  token = NextToken(in);
357  } else {
358  cout << endl;
359  cerr << "Expected input index or 'end' in identifier argument list of similarity term: " << name << endl;
360  exit(1);
361  }
362  int inc = 1;
363  int end = start;
364  if (token == COLON) {
365  token = NextToken(in);
366  if (token == NAME && token._Name == "end") {
367  end = -1;
368  token = NextToken(in);
369  if (token == MINUS) {
370  token = NextToken(in);
371  if (token != NUMBER || !IsInteger(token._Number) || token._Integer < 0) {
372  cout << endl;
373  cerr << "Expected positive integer after 'end-' in input identifier of similarity term: " << name << endl;
374  exit(1);
375  }
376  end = -token._Integer;
377  token = NextToken(in);
378  }
379  } else if (token == NUMBER) {
380  if (!IsInteger(token._Number) || token._Integer < 0) {
381  cout << endl;
382  cerr << "Expected positive integer after : in input identifier of similarity term: " << name << endl;
383  exit(1);
384  }
385  end = token._Integer;
386  token = NextToken(in);
387  } else {
388  cout << endl;
389  cerr << "Expected input index, 'end' or increment after : in input identifier of similarity term: " << name << endl;
390  exit(1);
391  }
392  if (token == COLON) {
393  inc = end;
394  end = start;
395  token = NextToken(in);
396  if (token == NAME && token._Name == "end") {
397  end = -1;
398  token = NextToken(in);
399  if (token == MINUS) {
400  token = NextToken(in);
401  if (token != NUMBER || !IsInteger(token._Number) || token._Integer < 0) {
402  cout << endl;
403  cerr << "Expected positive integer after 'end-' in input identifier of similarity term: " << name << endl;
404  exit(1);
405  }
406  end = -token._Integer;
407  token = NextToken(in);
408  }
409  } else if (token == NUMBER) {
410  if (!IsInteger(token._Number) || token._Integer < 0) {
411  cout << endl;
412  cerr << "Expected positive integer after : in input identifier of similarity term: " << name << endl;
413  exit(1);
414  }
415  end = token._Integer;
416  token = NextToken(in);
417  } else {
418  cout << endl;
419  cerr << "Expected input index or 'end' after second : in input identifier of similarity term: " << name << endl;
420  exit(1);
421  }
422  }
423  }
424  if (end < 0) end += max_index + 1;
425  if (start < 1 || end < start || inc == 0) {
426  cout << endl;
427  cerr << "Invalid index (range) in input identifier of energy term: " << name << endl;
428  exit(1);
429  }
430  if (start > max_index || end > max_index) {
431  cout << endl;
432  cerr << "Not enough input " << type_name << " or invalid index (range) in " << id_name << " of similarity term: " << name << endl;
433  exit(1);
434  }
435  for (int i = start - 1; i < end; i += inc) index.push_back(i);
436  if (token == RB) {
437  if (!inside_square_brackets) {
438  cout << endl;
439  cerr << "Found ] without matching [ in input identifier of similarity term: " << name << endl;
440  exit(1);
441  }
442  inside_square_brackets = false;
443  token = NextToken(in);
444  } else if (token == END) {
445  cout << endl;
446  cerr << "Expected ] after input index (range) in input identifier of similarity term: " << name << endl;
447  exit(1);
448  }
449  } while (inside_square_brackets);
450  if (token != RP) {
451  cout << endl;
452  cerr << "Expected ) after input index (range) in input identifier of similarity term: " << name << endl;
453  exit(1);
454  }
455  token = NextToken(in);
456  } else {
457  int i = -1;
458  if (FromString(token._Name.c_str() + 1, i) && i > 0 &&
459  token._Name == (token._Name.substr(0, 1) + ToString(i))) {
460  if (i > max_index) {
461  cout << endl;
462  cerr << "Not enought input " << type_name << " or invalid index in " << id_name << " of similarity term: " << name << endl;
463  exit(1);
464  }
465  index.push_back(i - 1);
466  token = NextToken(in);
467  } else {
468  cout << endl;
469  cerr << "Not enough input " << type_name << " or invalid " << id_name << " " << token._Name << " in argument list of similarity term: " << name << endl;
470  exit(1);
471  }
472  }
473  return index;
474  }
475 
476 public:
477 
478  // ---------------------------------------------------------------------------
479  /// Substitute single substring by given value
480  template <class T>
481  static string Substitute(const string &s, const char *var, T value)
482  {
483  size_t pos = s.find(var);
484  if (pos == string::npos) return s;
485  return s.substr(0, pos) + ToString(value) + s.substr(pos + strlen(var));
486  }
487 
488 protected:
489 
490  // ---------------------------------------------------------------------------
491  /// Name of image dissimilarity term
492  string TermName(const string &str, const ImageSimilarityInfo &info, int i = -1) const
493  {
494  string name(str);
495  name = Substitute(name, "{t}", info._TargetIndex + 1);
496  name = Substitute(name, "{s}", info._SourceIndex + 1);
497  if (i > -1) name = Substitute(name, "{i}", i + 1);
498  return name;
499  }
500 
501  // ---------------------------------------------------------------------------
502  /// Name of point set distance term
503  string TermName(const string &str, const PointSetDistanceInfo &info, int i = -1) const
504  {
505  string name(str);
506  name = Substitute(name, "{t}", info._TargetIndex + 1);
507  name = Substitute(name, "{s}", info._SourceIndex + 1);
508  if (i > -1) name = Substitute(name, "{i}", i + 1);
509  return name;
510  }
511 
512  // ---------------------------------------------------------------------------
513  /// Name of point set constraint term
514  string TermName(const string &str, const PointSetConstraintInfo &info, int i = -1) const
515  {
516  string name(str);
517  name = Substitute(name, "{n}", info._PointSetIndex + 1);
518  name = Substitute(name, "{t}", info._PointSetIndex + 1);
519  if (info._RefPointSetIndex > -1) {
520  name = Substitute(name, "{s}", info._RefPointSetIndex + 1);
521  } else if (info._RefImageIndex > -1) {
522  name = Substitute(name, "{s}", info._RefImageIndex + 1);
523  }
524  if (i > -1) name = Substitute(name, "{i}", i + 1);
525  return name;
526  }
527 
528  // ---------------------------------------------------------------------------
529  /// Parse and store information about next energy term
530  void ParseEnergyTerm(istream &in, Token &token, int nimages, int npsets)
531  {
532  double weight = Number(in, token);
533  if (token == MUL) {
534  token = NextToken(in);
535  }
536  if (token != NAME) {
537  cout << endl;
538  cerr << "Expected similarity or constraint name after optional weight value" << endl;
539  exit(1);
540  }
541 
542  string name = token._Name;
543  TransformationInfo target_transformation;
544  TransformationInfo source_transformation;
545  Array<int> target_index;
546  Array<int> source_index;
547  bool source_is_image = false;
548 
549  // Determine possible type of energy term
550  //
551  // Note: Name may be valid for more than just one type of energy term.
552  // Therefore, do not use "else if" below. Instead, decide which
553  // type of term it is later on when examining the term arguments.
554  enum { SIM, PDM, PCM, CM , MSDE };
555  bool term_is_[] = { false, false, false, false, false };
556 
557  SimilarityMeasure similarity = _Filter->_SimilarityMeasure;
558  PointSetDistanceMeasure pdm = _Filter->_PointSetDistanceMeasure;
559  ConstraintMeasure constraint;
560 
561  // InternalForceTerm enum only available when MIRTK_HAVE_Deformable
562  // Hence, use EnergyMeasure enumeration of Common module instead
563  EnergyMeasure measure;
564  FromString(name.c_str(), measure);
565  const string lname = ToLower(name);
566  term_is_[PCM ] = (IFT_Begin < measure && measure < IFT_End);
567  term_is_[SIM ] = ((lname == "sim") || FromString(name.c_str(), similarity));
568  term_is_[PDM ] = ((lname == "pdm") || FromString(name.c_str(), pdm));
569  term_is_[CM ] = ( FromString(name.c_str(), constraint));
570  term_is_[MSDE] = (measure == EM_MeanSquaredDisplacementError);
571  if (!term_is_[SIM] && !term_is_[PDM] && !term_is_[PCM] && !term_is_[CM] && !term_is_[MSDE]) {
572  cout << endl;
573  cerr << "Unknown energy term: " << name << endl;
574  exit(1);
575  }
576 
577  bool default_measure = (term_is_[SIM] && lname == "sim") ||
578  (term_is_[PDM] && lname == "pdm");
579 
580  // Custom name
581  token = NextToken(in);
582  if (token == LB) {
583  token = NextToken(in, false);
584  name.clear();
585  bool var_name = false;
586  while (token == NAME || token == NUMBER || token == SPACE || token == COLON ||
587  token == PLUS || token == MINUS || token == MUL || token == DIV ||
588  token == CARET || token == CIRC || token == LCB || token == RCB) {
589  if (token == SPACE) name += ' ';
590  else if (token == COLON) name += ':';
591  else if (token == PLUS) name += '+';
592  else if (token == MINUS) name += '-';
593  else if (token == MUL) name += '*';
594  else if (token == DIV) name += '/';
595  else if (token == CARET) name += '^';
596  else if (token == CIRC) name += 'o';
597  else if (token == NUMBER) name += ToString(token._Number);
598  else if (token == LCB) {
599  if (var_name) {
600  cerr << "Expected closing } in custom energy term name" << name << endl;
601  exit(1);
602  }
603  var_name = true;
604  name += '{';
605  } else if (token == RCB) {
606  if (!var_name) {
607  cerr << "Found closing } without opening { in custom energy term name: " << name << endl;
608  exit(1);
609  }
610  var_name = false;
611  name += '}';
612  } else if (token == NAME) {
613  name += token._Name;
614  } else {
615  cerr << "Internal error: Unhandled token in energy term name" << endl;
616  exit(1);
617  }
618  token = NextToken(in, false);
619  }
620  if (var_name) {
621  cerr << "Missing closing } in custom energy term name: " << name << endl;
622  exit(1);
623  }
624  if (token != RB) {
625  cout << endl;
626  cerr << "Expected closing ] after custom energy term name: " << name << endl;
627  exit(1);
628  }
629  token = NextToken(in);
630  }
631 
632  // Arguments
633  if (token == LP) {
634  token = NextToken(in);
635  if (token == NAME) {
636  // ---------------------------------------------------------------------
637  // left composition with transformation as needed for point sets
638  if (token._Name == "T") {
639  token = NextToken(in);
640  if (token == RP) {
641  if (!term_is_[CM] && !term_is_[MSDE]) {
642  cout << endl;
643  cerr << "Transformation identifier cannot be only argument of energy term \"" << name << "\"," << endl;
644  cerr << "which is not a (known) transformation constraint (i.e., regularization term)." << endl;
645  exit(1);
646  }
647  } else {
648  if (token == CARET) {
649  token = NextToken(in);
650  if (token != NUMBER && token != MINUS && token != PLUS) {
651  cout << endl;
652  cerr << "Expected exponent after ^ of transformation composition in argument list of energy term: " << name << endl;
653  exit(1);
654  }
655  target_transformation = Number(in, token);
656  } else {
657  target_transformation = 1.0;
658  }
659  if (token != CIRC) {
660  cout << endl;
661  cerr << "Expected function composition sign (o) after transformation identifier in argument list of energy term: " << name << endl;
662  exit(1);
663  }
664  token = NextToken(in);
665  term_is_[CM ] = false;
666  term_is_[MSDE] = false;
667  }
668  }
669  // ---------------------------------------------------------------------
670  if ((term_is_[CM] || term_is_[MSDE]) && token == RP) {
671  // Term must be a transformation constraint
672  term_is_[SIM] = false;
673  term_is_[PDM] = false;
674  term_is_[PCM] = false;
675  // ---------------------------------------------------------------------
676  } else if (!(target_index = InputIndex(in, token, name, IMAGE, nimages)).empty()) {
677  // Term must be an image similarity
678  if (!term_is_[SIM]) {
679  cout << endl;
680  cerr << "Image identifier cannot be argument of energy term \"" << name << "\"," << endl;
681  cerr << "which is not a (known) pairwise image similarity term." << endl;
682  exit(1);
683  }
684  term_is_[PDM] = false;
685  term_is_[CM] = false;
686  term_is_[MSDE] = false;
687  // Composition with transformation from left not valid
688  if (target_transformation) {
689  cout << endl;
690  cerr << "Invalid function composition (o) of first image in argument list of fiducial registration error: " << name << endl;
691  cerr << "Note that the (source) image is transformed using the notation I2 o T, not T o I2!" << endl;
692  exit(1);
693  }
694  // Target image argument
695  if (token == CIRC) {
696  token = NextToken(in);
697  if (token != NAME || token._Name != "T") {
698  cout << endl;
699  cerr << "Expected transformation after function composition sign (o) in argument list of similarity term: " << name << endl;
700  exit(1);
701  }
702  token = NextToken(in);
703  if (token == CARET) {
704  token = NextToken(in);
705  if (token != NUMBER && token != MINUS && token != PLUS) {
706  cout << endl;
707  cerr << "Expected exponent after ^ of transformation composition in argument list of similarity term: " << name << endl;
708  exit(1);
709  }
710  target_transformation = Number(in, token);
711  } else {
712  target_transformation = 1.0;
713  }
714  }
715  // Source image argument
716  if (token != COMMA) {
717  cout << endl;
718  cerr << "Expected separating , after first image identifier in argument list of similarity term: " << name << endl;
719  exit(1);
720  }
721  token = NextToken(in);
722  if (token == NAME) source_index = InputIndex(in, token, name, IMAGE, nimages);
723  if (source_index.empty()) {
724  cout << endl;
725  cerr << "Expected second image identifier after , of argument list of similarity term: " << name << endl;
726  exit(1);
727  }
728  if (token == CIRC) {
729  token = NextToken(in);
730  if (token != NAME || token._Name != "T") {
731  cout << endl;
732  cerr << "Expected transformation after function composition sign (o) in argument list of similarity term: " << name << endl;
733  exit(1);
734  }
735  token = NextToken(in);
736  if (token == CARET) {
737  token = NextToken(in);
738  if (token != NUMBER && token != MINUS && token != PLUS) {
739  cout << endl;
740  cerr << "Expected exponent after ^ of transformation composition in argument list of similarity term: " << name << endl;
741  exit(1);
742  }
743  source_transformation = Number(in, token);
744  } else {
745  source_transformation = 1.0;
746  }
747  }
748  // By default, transform second image if no explicit transformation was specified
749  if (target_transformation.IsIdentity() && source_transformation.IsIdentity()) {
750  source_transformation = 1.0;
751  }
752  source_is_image = true;
753  // ---------------------------------------------------------------------
754  } else if (!(target_index = InputIndex(in, token, name, POLYDATA, npsets)).empty()) {
755  // Term must be a point set distance or constraint
756  if (!term_is_[PDM] && !term_is_[PCM]) {
757  cout << endl;
758  cerr << "Point set identifier cannot be argument of energy term \"" << name << "\"," << endl;
759  cerr << "which is neither a (known) pairwise point set distance or internal force term." << endl;
760  exit(1);
761  }
762  // Term cannot be image similarity or transformation constraint
763  term_is_[SIM] = false;
764  term_is_[CM] = false;
765  term_is_[MSDE] = false;
766  // Target data set argument
767  if (token == CIRC) {
768  cout << endl;
769  cerr << "Invalid function composition (o) of point set in argument list of energy term: " << name << endl;
770  cerr << "Note that data sets are transformed using the notation T o S1, not S1 o T!" << endl;
771  exit(1);
772  }
773  if (token == RP) {
774  // Term must be a point set constraint (i.e., internal force)
775  if (!term_is_[PCM]) {
776  cout << endl;
777  cerr << "Point set identifier cannot be argument of energy term \"" << name << "\"," << endl;
778  cerr << "which is not a (known) internal point set force term." << endl;
779  exit(1);
780  }
781  term_is_[PDM] = false;
782  // By default, transform data set if no explicit transformation was specified
783  if (target_transformation.IsIdentity()) {
784  target_transformation = 1.0;
785  }
786  } else {
787  // Source data set argument
788  if (token != COMMA) {
789  cout << endl;
790  cerr << "Expected separating , after first point set identifier in argument list of point set energy term: " << name << endl;
791  exit(1);
792  }
793  token = NextToken(in);
794  if (token != NAME) {
795  cout << endl;
796  cerr << "Expected transformation or second input identifier after , of argument list of point set energy term: " << name << endl;
797  exit(1);
798  }
799  source_index = InputIndex(in, token, name, POLYDATA, npsets);
800  if (source_index.empty()) {
801  if (term_is_[PCM]) {
802  source_index = InputIndex(in, token, name, IMAGE, nimages);
803  if (source_index.empty()) {
804  cout << endl;
805  if (token == NAME && token._Name == "T") {
806  cerr << "Expected second input identifier after , of argument list of point set constraint term: " << name << endl;
807  cerr << "Note that second data set is only used to define reference time frame to which the first" << endl;
808  cerr << "point set is transformed to and therefore no transformation of the second data set is allowed." << endl;
809  } else {
810  cerr << "Expected second input identifier after , of argument list of point set constraint term: " << name << endl;
811  }
812  exit(1);
813  }
814  source_is_image = true;
815  } else {
816  if (token != NAME || token._Name != "T") {
817  cout << endl;
818  cerr << "Expected (optionally transformed) second input identifier after , of argument list of point set energy term: " << name << endl;
819  exit(1);
820  }
821  token = NextToken(in);
822  if (token == CARET) {
823  token = NextToken(in);
824  if (token != NUMBER && token != MINUS && token != PLUS) {
825  cout << endl;
826  cerr << "Expected exponent after ^ of transformation composition in argument list of point set energy term: " << name << endl;
827  exit(1);
828  }
829  source_transformation = Number(in, token);
830  } else {
831  source_transformation = 1.0;
832  }
833  if (token != CIRC) {
834  cout << endl;
835  cerr << "Expected function composition sign (o) after transformation identifier in argument list of point set energy term: " << name << endl;
836  exit(1);
837  }
838  token = NextToken(in);
839  if (token == NAME) source_index = InputIndex(in, token, name, POLYDATA, npsets);
840  if (source_index.empty()) {
841  cout << endl;
842  cerr << "Expected point set identifier after function composition sign (o) in argument list of point set distance: " << name << endl;
843  exit(1);
844  }
845  }
846  }
847  if (token == CIRC) {
848  cout << endl;
849  cerr << "Invalid function composition (o) of second data set in argument list of point set energy term: " << name << endl;
850  if (term_is_[PDM]) {
851  cerr << "Note that (source) point sets are transformed using the notation T^-1 o S2, not S2 o T^-1!" << endl;
852  }
853  exit(1);
854  }
855  // By default, transform first data set if no explicit transformation was specified
856  if (target_transformation.IsIdentity() && source_transformation.IsIdentity()) {
857  target_transformation = 1.0;
858  }
859  }
860  // ---------------------------------------------------------------------
861  } else {
862  cout << endl;
863  cerr << "Expected ";
864  if (term_is_[SIM]) cerr << "image";
865  if (term_is_[PDM] || term_is_[PCM]) {
866  if (term_is_[SIM]) cerr << " or ";
867  cerr << "point set";
868  }
869  if (term_is_[CM] || term_is_[MSDE]) {
870  if (term_is_[SIM] || term_is_[PDM] || term_is_[PCM]) cerr << " or ";
871  cerr << "transformation";
872  }
873  cerr << " identifier as argument of energy term: " << name << endl;
874  exit(1);
875  }
876  }
877  if (token != RP) {
878  cout << endl;
879  cerr << "Expected closing ) after argument list of energy term: " << name << endl;
880  exit(1);
881  }
882  token = NextToken(in);
883  }
884  if (target_index.empty() && source_index.empty()) {
885  if (!term_is_[CM] && !term_is_[MSDE]) {
886  cout << endl;
887  cerr << "Expected ";
888  if (term_is_[SIM]) cerr << "image";
889  if (term_is_[PDM] || term_is_[PCM]) {
890  if (term_is_[SIM]) cerr << " or ";
891  cerr << "point set";
892  }
893  cerr << " identifiers as argument of energy term: " << name << endl;
894  exit(1);
895  }
896  term_is_[SIM] = false;
897  term_is_[PDM] = false;
898  term_is_[PCM] = false;
899  }
900 
901  // Add energy term structure with parsed information
902  if (term_is_[SIM]) {
903 
904  if (term_is_[PDM] || term_is_[PCM] || term_is_[CM] || term_is_[MSDE]) {
905  cout << endl;
906  cerr << "Ambiguous energy term: " << name << endl;
907  exit(1);
908  }
909  if (target_index.size() == 0 || source_index.size() == 0) {
910  cout << endl;
911  cerr << "Missing image identifier in argument list of similarity term: " << name << endl;
912  exit(1);
913  }
914  if (!source_is_image) {
915  cout << endl;
916  cerr << "Only images allowed as inputs of the image dissimilarity term: " << name << endl;
917  exit(1);
918  }
919 
920  if (target_index.size() > 1) {
921  if (source_index.size() != 1 && source_index.size() != target_index.size()) {
922  cout << endl;
923  cerr << "Mismatch of image index ranges in argument list of similarity term: " << name << endl;
924  exit(1);
925  }
926  weight /= target_index.size();
927  for (size_t t = 0; t < target_index.size(); ++t) {
928  size_t s = (source_index.size() == 1 ? 0 : t);
929  ImageSimilarityInfo info;
930  info._DefaultSign = default_measure;
931  info._Weight = weight;
932  info._Measure = similarity;
933  info._TargetIndex = target_index[t];
934  info._TargetTransformation = target_transformation;
935  info._SourceIndex = source_index[s];
936  info._SourceTransformation = source_transformation;
937  info._Name = TermName(name, info, static_cast<int>(t));
938  _Filter->_ImageSimilarityInfo.push_back(info);
939  }
940  } else {
941  if (target_index.size() != 1) {
942  cout << endl;
943  cerr << "Mismatch of image index ranges in argument list of similarity term: " << name << endl;
944  exit(1);
945  }
946  weight /= source_index.size();
947  for (size_t s = 0; s < source_index.size(); ++s) {
948  ImageSimilarityInfo info;
949  info._DefaultSign = default_measure;
950  info._Weight = weight;
951  info._Measure = similarity;
952  info._TargetIndex = target_index[0];
953  info._TargetTransformation = target_transformation;
954  info._SourceIndex = source_index[s];
955  info._SourceTransformation = source_transformation;
956  info._Name = TermName(name, info, static_cast<int>(s));
957  _Filter->_ImageSimilarityInfo.push_back(info);
958  }
959  }
960 
961  }
962 
963  if (term_is_[PDM]) {
964 
965  if (term_is_[SIM] || term_is_[PCM] || term_is_[CM] || term_is_[MSDE]) {
966  cout << endl;
967  cerr << "Ambiguous energy term: " << name << endl;
968  exit(1);
969  }
970  if (target_index.size() == 0 || source_index.size() == 0) {
971  cout << endl;
972  cerr << "Missing point set identifier in argument list of point set distance term: " << name << endl;
973  exit(1);
974  }
975  if (source_is_image) {
976  cout << endl;
977  cerr << "Only point sets allowed as inputs of the point set distance term: " << name << endl;
978  exit(1);
979  }
980 
981  if (target_index.size() > 1) {
982  if (source_index.size() != 1 && source_index.size() != target_index.size()) {
983  cout << endl;
984  cerr << "Mismatch of point set index ranges in argument list of point set distance term: " << name << endl;
985  exit(1);
986  }
987  weight /= target_index.size();
988  for (size_t t = 0; t < target_index.size(); ++t) {
989  size_t s = (source_index.size() == 1 ? 0 : t);
990  PointSetDistanceInfo info;
991  info._DefaultSign = default_measure;
992  info._Weight = weight;
993  info._Measure = pdm;
994  info._TargetIndex = target_index[t];
995  info._TargetTransformation = target_transformation;
996  info._SourceIndex = source_index[s];
997  info._SourceTransformation = source_transformation;
998  info._Name = TermName(name, info, static_cast<int>(t));
999  _Filter->_PointSetDistanceInfo.push_back(info);
1000  }
1001  } else {
1002  if (target_index.size() != 1) {
1003  cout << endl;
1004  cerr << "Mismatch of point set index ranges in argument list of point set distance term: " << name << endl;
1005  exit(1);
1006  }
1007  weight /= source_index.size();
1008  for (size_t s = 0; s < source_index.size(); ++s) {
1009  PointSetDistanceInfo info;
1010  info._DefaultSign = default_measure;
1011  info._Weight = weight;
1012  info._Measure = pdm;
1013  info._TargetIndex = target_index[0];
1014  info._TargetTransformation = target_transformation;
1015  info._SourceIndex = source_index[s];
1016  info._SourceTransformation = source_transformation;
1017  info._Name = TermName(name, info, static_cast<int>(s));
1018  _Filter->_PointSetDistanceInfo.push_back(info);
1019  }
1020  }
1021 
1022  }
1023 
1024  if (term_is_[PCM]) {
1025 
1026  if (term_is_[SIM] || term_is_[PDM] || term_is_[CM] || term_is_[MSDE]) {
1027  cout << endl;
1028  cerr << "Ambiguous energy term: " << name << endl;
1029  exit(1);
1030  }
1031  if (target_index.size() == 0) {
1032  cout << endl;
1033  cerr << "Missing point set identifier in argument list of internal point set force term: " << name << endl;
1034  exit(1);
1035  }
1036 
1037  if (source_index.size() > 0) {
1038  if (target_index.size() > 1) {
1039  if (source_index.size() != 1 && source_index.size() != target_index.size()) {
1040  cout << endl;
1041  cerr << "Mismatch of input index ranges in argument list of point set constraint term: " << name << endl;
1042  exit(1);
1043  }
1044  weight /= target_index.size();
1045  for (size_t t = 0; t < target_index.size(); ++t) {
1046  size_t s = (source_index.size() == 1 ? 0 : t);
1047  PointSetConstraintInfo info;
1048  info._Weight = weight;
1049  info._Measure = measure;
1050  info._PointSetIndex = target_index[t];
1051  info._Transformation = target_transformation;
1052  if (source_is_image) {
1053  info._RefPointSetIndex = -1;
1054  info._RefImageIndex = source_index[s];
1055  } else {
1056  info._RefPointSetIndex = source_index[s];
1057  info._RefImageIndex = -1;
1058  }
1059  info._Name = TermName(name, info, static_cast<int>(t));
1060  _Filter->_PointSetConstraintInfo.push_back(info);
1061  }
1062  } else {
1063  if (target_index.size() != 1) {
1064  cout << endl;
1065  cerr << "Mismatch of input index ranges in argument list of point set constraint term: " << name << endl;
1066  exit(1);
1067  }
1068  weight /= source_index.size();
1069  for (size_t s = 0; s < source_index.size(); ++s) {
1070  PointSetConstraintInfo info;
1071  info._Weight = weight;
1072  info._Measure = measure;
1073  info._PointSetIndex = target_index[0];
1074  info._Transformation = target_transformation;
1075  if (source_is_image) {
1076  info._RefPointSetIndex = -1;
1077  info._RefImageIndex = source_index[s];
1078  } else {
1079  info._RefPointSetIndex = source_index[s];
1080  info._RefImageIndex = -1;
1081  }
1082  info._Name = TermName(name, info, static_cast<int>(s));
1083  _Filter->_PointSetConstraintInfo.push_back(info);
1084  }
1085  }
1086  } else {
1087  weight /= target_index.size();
1088  for (size_t t = 0; t < target_index.size(); ++t) {
1089  PointSetConstraintInfo info;
1090  info._Weight = weight;
1091  info._Measure = measure;
1092  info._PointSetIndex = target_index[t];
1093  info._Transformation = target_transformation;
1094  info._RefPointSetIndex = -1;
1095  info._RefImageIndex = -1;
1096  // {i} substituted by GenericRegistrationFilter::AddPointSetConstraint
1097  info._Name = TermName(name, info, -1);
1098  _Filter->_PointSetConstraintInfo.push_back(info);
1099  }
1100  }
1101 
1102  }
1103 
1104  if (term_is_[CM]) {
1105 
1106  if (term_is_[SIM] || term_is_[PDM] || term_is_[PCM] || term_is_[MSDE]) {
1107  cout << endl;
1108  cerr << "Ambiguous energy term: " << name << endl;
1109  exit(1);
1110  }
1111 
1112  ConstraintInfo info;
1113  info._Weight = weight;
1114  info._Measure = constraint;
1115  info._Name = name;
1116  _Filter->_ConstraintInfo.push_back(info);
1117 
1118  }
1119 
1120  if (term_is_[MSDE]) {
1121 
1122  if (term_is_[SIM] || term_is_[PDM] || term_is_[PCM] || term_is_[CM]) {
1123  cout << endl;
1124  cerr << "Ambiguous energy term: " << name << endl;
1125  exit(1);
1126  }
1127 
1128  _Filter->TargetTransformationErrorName(name);
1129  _Filter->TargetTransformationErrorWeight(weight);
1130 
1131  }
1132 
1133  if (!term_is_[SIM] && !term_is_[PDM] && !term_is_[PCM] && !term_is_[CM] && !term_is_[MSDE]) {
1134  cout << endl;
1135  cerr << "Unknown enery term: " << name << endl;
1136  exit(1);
1137  }
1138  }
1139 
1140 public:
1141 
1142  // ---------------------------------------------------------------------------
1143  /// Constructor
1145  :
1146  _Filter(filter)
1147  {}
1148 
1149  // ---------------------------------------------------------------------------
1150  /// Parse energy function
1151  void ParseEnergyFormula(const string &energy_formula, int nimages = -1, int npsets = -1)
1152  {
1153  istringstream formula(energy_formula);
1154  Token token (END);
1155 
1156  _Filter->_ImageSimilarityInfo .clear();
1157  _Filter->_PointSetDistanceInfo .clear();
1158  _Filter->_PointSetConstraintInfo.clear();
1159  _Filter->_ConstraintInfo .clear();
1160 
1161  if (nimages < 0) nimages = _Filter->NumberOfImages();
1162  if (npsets < 0) npsets = _Filter->NumberOfPointSets();
1163 
1164  token = NextToken(formula);
1165  while (token != END) {
1166  ParseEnergyTerm(formula, token, nimages, npsets);
1167  }
1168  }
1169 
1170 }; // RegistrationEnergyParser
1171 
1172 
1173 } // namespace mirtk
TransformationInfo _TargetTransformation
Target transformation identifier.
int NumberOfImages() const
Number of input images.
TransformationInfo _Transformation
Point set transformation identifier.
TransformationInfo _SourceTransformation
Source transformation identifier.
string TermName(const string &str, const ImageSimilarityInfo &info, int i=-1) const
Name of image dissimilarity term.
static string Substitute(const string &s, const char *var, T value)
Substitute single substring by given value.
TransformationInfo _SourceTransformation
Source transformation identifier.
void ParseEnergyTerm(istream &in, Token &token, int nimages, int npsets)
Parse and store information about next energy term.
Mean squared deviation from given deformation.
int NumberOfPointSets() const
Number of input point sets.
bool _DefaultSign
Whether to use default sign of similarity.
Array< ConstraintInfo > _ConstraintInfo
Parsed constraint(s)
Structure storing information about transformation instance.
bool IsInteger(const char *str)
Check if given string is an integer value.
Definition: String.h:194
string ToLower(const string &)
Convert string to lowercase letters.
Definition: IOConfig.h:41
TransformationInfo _TargetTransformation
Target transformation identifier.
void ParseEnergyFormula(const string &energy_formula, int nimages=-1, int npsets=-1)
Parse energy function.
RegistrationEnergyParser(GenericRegistrationFilter *filter)
Constructor.
MIRTKCU_API bool operator==(const float1 &a, const float1 &b)
Check two 1D vectors for equality.
Definition: Math.h:731
string TermName(const string &str, const PointSetConstraintInfo &info, int i=-1) const
Name of point set constraint term.
Array< PointSetConstraintInfo > _PointSetConstraintInfo
Unused.
string ToString(const EnergyMeasure &value, int w, char c, bool left)
Convert energy measure enumeration value to string.
bool FromString(const char *str, EnergyMeasure &value)
Convert energy measure string to enumeration value.
EnergyMeasure
Enumeration of all available energy terms.
Definition: EnergyMeasure.h:31
Array< PointSetDistanceInfo > _PointSetDistanceInfo
Unused.
SimilarityMeasure _Measure
Type of similarity measure.
PointSetDistanceMeasure _Measure
Measure of polydata distance.
bool _DefaultSign
Whether to use default sign of distance measure.
Array< ImageSimilarityInfo > _ImageSimilarityInfo
Parsed similarity measure(s)
string TermName(const string &str, const PointSetDistanceInfo &info, int i=-1) const
Name of point set distance term.