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  */
68 class RegistrationEnergyParser
69 {
70   GenericRegistrationFilter *_Filter;
71 
72   typedef GenericRegistrationFilter::TransformationInfo     TransformationInfo;
73   typedef GenericRegistrationFilter::ImageSimilarityInfo    ImageSimilarityInfo;
74   typedef GenericRegistrationFilter::PointSetDistanceInfo   PointSetDistanceInfo;
75   typedef GenericRegistrationFilter::PointSetConstraintInfo PointSetConstraintInfo;
76   typedef GenericRegistrationFilter::ConstraintInfo         ConstraintInfo;
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 
TokenToken120     Token(TokenEnum token)  : _Token(token)                      {}
TokenToken121     Token(char      c)      : _Token(TokenEnum(c))               {}
TokenToken122     Token(double    number) : _Token(NUMBER), _Number(number),
123                               _Integer(static_cast<int>(number)) {}
TokenToken124     Token(string name) : _Token(NAME),   _Name(name)        {}
125 
TokenToken126     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
Number(istream & in,Token & token)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
IsInteger(double number)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
InputIndex(istream & in,Token & token,const string & name,DataType type,int max_index)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>
Substitute(const string & s,const char * var,T value)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
ParseEnergyTerm(istream & in,Token & token,int nimages,int npsets)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
RegistrationEnergyParser(GenericRegistrationFilter * filter)1144   RegistrationEnergyParser(GenericRegistrationFilter *filter)
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
1174