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