comparison OrthancStone/Sources/Toolbox/DicomStructureSet.cpp @ 2154:f8cbfd7175c3

fix parsing of structure sets
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 27 Sep 2024 15:05:41 +0200
parents 16c01cc201e7
children 917e40af6b45
comparison
equal deleted inserted replaced
2153:32bfccdc030f 2154:f8cbfd7175c3
119 #endif 119 #endif
120 120
121 121
122 namespace OrthancStone 122 namespace OrthancStone
123 { 123 {
124 static const Orthanc::DicomTag DICOM_TAG_CONTOUR_DATA(0x3006, 0x0050);
124 static const Orthanc::DicomTag DICOM_TAG_CONTOUR_GEOMETRIC_TYPE(0x3006, 0x0042); 125 static const Orthanc::DicomTag DICOM_TAG_CONTOUR_GEOMETRIC_TYPE(0x3006, 0x0042);
125 static const Orthanc::DicomTag DICOM_TAG_CONTOUR_IMAGE_SEQUENCE(0x3006, 0x0016); 126 static const Orthanc::DicomTag DICOM_TAG_CONTOUR_IMAGE_SEQUENCE(0x3006, 0x0016);
126 static const Orthanc::DicomTag DICOM_TAG_CONTOUR_SEQUENCE(0x3006, 0x0040); 127 static const Orthanc::DicomTag DICOM_TAG_CONTOUR_SEQUENCE(0x3006, 0x0040);
127 static const Orthanc::DicomTag DICOM_TAG_CONTOUR_DATA(0x3006, 0x0050);
128 static const Orthanc::DicomTag DICOM_TAG_NUMBER_OF_CONTOUR_POINTS(0x3006, 0x0046); 128 static const Orthanc::DicomTag DICOM_TAG_NUMBER_OF_CONTOUR_POINTS(0x3006, 0x0046);
129 static const Orthanc::DicomTag DICOM_TAG_REFERENCED_ROI_NUMBER(0x3006, 0x0084);
129 static const Orthanc::DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155); 130 static const Orthanc::DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155);
130 static const Orthanc::DicomTag DICOM_TAG_ROI_CONTOUR_SEQUENCE(0x3006, 0x0039); 131 static const Orthanc::DicomTag DICOM_TAG_ROI_CONTOUR_SEQUENCE(0x3006, 0x0039);
131 static const Orthanc::DicomTag DICOM_TAG_ROI_DISPLAY_COLOR(0x3006, 0x002a); 132 static const Orthanc::DicomTag DICOM_TAG_ROI_DISPLAY_COLOR(0x3006, 0x002a);
132 static const Orthanc::DicomTag DICOM_TAG_ROI_NAME(0x3006, 0x0026); 133 static const Orthanc::DicomTag DICOM_TAG_ROI_NAME(0x3006, 0x0026);
134 static const Orthanc::DicomTag DICOM_TAG_ROI_NUMBER(0x3006, 0x0022);
133 static const Orthanc::DicomTag DICOM_TAG_RT_ROI_INTERPRETED_TYPE(0x3006, 0x00a4); 135 static const Orthanc::DicomTag DICOM_TAG_RT_ROI_INTERPRETED_TYPE(0x3006, 0x00a4);
134 static const Orthanc::DicomTag DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE(0x3006, 0x0080); 136 static const Orthanc::DicomTag DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE(0x3006, 0x0080);
135 static const Orthanc::DicomTag DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE(0x3006, 0x0020); 137 static const Orthanc::DicomTag DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE(0x3006, 0x0020);
136 138
137 139
386 { 388 {
387 #if STONE_TIME_BLOCKING_OPS 389 #if STONE_TIME_BLOCKING_OPS
388 boost::posix_time::ptime timerStart = boost::posix_time::microsec_clock::universal_time(); 390 boost::posix_time::ptime timerStart = boost::posix_time::microsec_clock::universal_time();
389 #endif 391 #endif
390 392
393 std::map<int, size_t> roiNumbersIndex;
394
391 DicomDatasetReader reader(tags); 395 DicomDatasetReader reader(tags);
392 396
393 size_t count, tmp; 397
394 if (!tags.GetSequenceSize(count, Orthanc::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE)) || 398 /**
395 !tags.GetSequenceSize(tmp, Orthanc::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE)) || 399 * 1. Read all the available ROIs.
396 tmp != count || 400 **/
397 !tags.GetSequenceSize(tmp, Orthanc::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE)) || 401
398 tmp != count) 402 {
399 { 403 size_t count;
400 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); 404 if (!tags.GetSequenceSize(count, Orthanc::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE)))
401 } 405 {
402 406 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
403 structures_.resize(count); 407 }
404 structureNamesIndex_.clear(); 408
405 409 structures_.resize(count);
406 for (size_t i = 0; i < count; i++) 410 structureNamesIndex_.clear();
407 { 411
408 structures_[i].interpretation_ = reader.GetStringValue 412 for (size_t i = 0; i < count; i++)
409 (Orthanc::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i, 413 {
410 DICOM_TAG_RT_ROI_INTERPRETED_TYPE), 414 int roiNumber;
411 "No interpretation"); 415 if (!reader.GetIntegerValue
412 416 (roiNumber, Orthanc::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i, DICOM_TAG_ROI_NUMBER)))
413 structures_[i].name_ = reader.GetStringValue 417 {
414 (Orthanc::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i, 418 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
415 DICOM_TAG_ROI_NAME), 419 }
416 "No name"); 420
417 421 if (roiNumbersIndex.find(roiNumber) != roiNumbersIndex.end())
418 if (structureNamesIndex_.find(structures_[i].name_) == structureNamesIndex_.end()) 422 {
419 { 423 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
420 structureNamesIndex_[structures_[i].name_] = i; 424 "Twice the same ROI number: " + boost::lexical_cast<std::string>(roiNumber));
421 } 425 }
422 else 426
423 { 427 roiNumbersIndex[roiNumber] = i;
424 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, 428
425 "RT-STRUCT with twice the same name for a structure: " + structures_[i].name_); 429 structures_[i].name_ = reader.GetStringValue
426 } 430 (Orthanc::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i, DICOM_TAG_ROI_NAME), "No name");
427 431 structures_[i].interpretation_ = "No interpretation";
428 Vector color; 432
429 if (FastParseVector(color, tags, Orthanc::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, 433 if (structureNamesIndex_.find(structures_[i].name_) == structureNamesIndex_.end())
430 DICOM_TAG_ROI_DISPLAY_COLOR)) && 434 {
431 color.size() == 3) 435 structureNamesIndex_[structures_[i].name_] = i;
432 { 436 }
433 structures_[i].red_ = ConvertColor(color[0]); 437 else
434 structures_[i].green_ = ConvertColor(color[1]); 438 {
435 structures_[i].blue_ = ConvertColor(color[2]); 439 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
436 } 440 "RT-STRUCT with twice the same name for a structure: " + structures_[i].name_);
437 else 441 }
438 { 442 }
439 structures_[i].red_ = 255; 443 }
440 structures_[i].green_ = 0; 444
441 structures_[i].blue_ = 0; 445
442 } 446 /**
443 447 * 2. Read the interpretation of the ROIs (if available).
444 size_t countSlices; 448 **/
445 if (!tags.GetSequenceSize(countSlices, Orthanc::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, 449
446 DICOM_TAG_CONTOUR_SEQUENCE))) 450 {
447 { 451 size_t count;
448 countSlices = 0; 452 if (!tags.GetSequenceSize(count, Orthanc::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE)))
449 } 453 {
450 454 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
451 LOG(INFO) << "New RT structure: \"" << structures_[i].name_ 455 }
452 << "\" with interpretation \"" << structures_[i].interpretation_ 456
453 << "\" containing " << countSlices << " slices (color: " 457 for (size_t i = 0; i < count; i++)
454 << static_cast<int>(structures_[i].red_) << "," 458 {
455 << static_cast<int>(structures_[i].green_) << "," 459 std::string interpretation;
456 << static_cast<int>(structures_[i].blue_) << ")"; 460 if (reader.GetDataset().GetStringValue(interpretation,
457 461 Orthanc::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i,
458 /** 462 DICOM_TAG_RT_ROI_INTERPRETED_TYPE)))
459 * These temporary variables avoid allocating many vectors in 463 {
460 * the loop below (indeed, "Orthanc::DicomPath" handles a 464 int roiNumber;
461 * "std::vector<PrefixItem>") 465 if (!reader.GetIntegerValue(roiNumber,
462 **/ 466 Orthanc::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i,
463 Orthanc::DicomPath countPointsPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, 467 DICOM_TAG_REFERENCED_ROI_NUMBER)))
464 DICOM_TAG_CONTOUR_SEQUENCE, 0, 468 {
465 DICOM_TAG_NUMBER_OF_CONTOUR_POINTS); 469 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
466 470 }
467 Orthanc::DicomPath geometricTypePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, 471
472 std::map<int, size_t>::const_iterator found = roiNumbersIndex.find(roiNumber);
473 if (found == roiNumbersIndex.end())
474 {
475 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
476 }
477
478 structures_[found->second].interpretation_ = interpretation;
479 }
480 }
481 }
482
483
484 /**
485 * 3. Read the contours.
486 **/
487
488 {
489 size_t count;
490 if (!tags.GetSequenceSize(count, Orthanc::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE)))
491 {
492 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
493 }
494
495 for (size_t i = 0; i < count; i++)
496 {
497 int roiNumber;
498 if (!reader.GetIntegerValue(roiNumber,
499 Orthanc::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
500 DICOM_TAG_REFERENCED_ROI_NUMBER)))
501 {
502 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
503 }
504
505 std::map<int, size_t>::const_iterator found = roiNumbersIndex.find(roiNumber);
506 if (found == roiNumbersIndex.end())
507 {
508 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
509 }
510
511 Structure& target = structures_[found->second];
512
513 Vector color;
514 if (FastParseVector(color, tags, Orthanc::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
515 DICOM_TAG_ROI_DISPLAY_COLOR)) &&
516 color.size() == 3)
517 {
518 target.red_ = ConvertColor(color[0]);
519 target.green_ = ConvertColor(color[1]);
520 target.blue_ = ConvertColor(color[2]);
521 }
522 else
523 {
524 target.red_ = 255;
525 target.green_ = 0;
526 target.blue_ = 0;
527 }
528
529 size_t countSlices;
530 if (!tags.GetSequenceSize(countSlices, Orthanc::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
531 DICOM_TAG_CONTOUR_SEQUENCE)))
532 {
533 countSlices = 0;
534 }
535
536 LOG(INFO) << "New RT structure: \"" << target.name_
537 << "\" with interpretation \"" << target.interpretation_
538 << "\" containing " << countSlices << " slices (color: "
539 << static_cast<int>(target.red_) << ","
540 << static_cast<int>(target.green_) << ","
541 << static_cast<int>(target.blue_) << ")";
542
543 /**
544 * These temporary variables avoid allocating many vectors in
545 * the loop below (indeed, "Orthanc::DicomPath" handles a
546 * "std::vector<PrefixItem>")
547 **/
548 Orthanc::DicomPath countPointsPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
468 DICOM_TAG_CONTOUR_SEQUENCE, 0, 549 DICOM_TAG_CONTOUR_SEQUENCE, 0,
469 DICOM_TAG_CONTOUR_GEOMETRIC_TYPE); 550 DICOM_TAG_NUMBER_OF_CONTOUR_POINTS);
470 551
471 Orthanc::DicomPath imageSequencePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, 552 Orthanc::DicomPath geometricTypePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
553 DICOM_TAG_CONTOUR_SEQUENCE, 0,
554 DICOM_TAG_CONTOUR_GEOMETRIC_TYPE);
555
556 Orthanc::DicomPath imageSequencePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
557 DICOM_TAG_CONTOUR_SEQUENCE, 0,
558 DICOM_TAG_CONTOUR_IMAGE_SEQUENCE);
559
560 // (3006,0039)[i] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155)
561 Orthanc::DicomPath referencedInstancePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
562 DICOM_TAG_CONTOUR_SEQUENCE, 0,
563 DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0,
564 DICOM_TAG_REFERENCED_SOP_INSTANCE_UID);
565
566 Orthanc::DicomPath contourDataPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
472 DICOM_TAG_CONTOUR_SEQUENCE, 0, 567 DICOM_TAG_CONTOUR_SEQUENCE, 0,
473 DICOM_TAG_CONTOUR_IMAGE_SEQUENCE); 568 DICOM_TAG_CONTOUR_DATA);
474 569
475 // (3006,0039)[i] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155) 570 for (size_t j = 0; j < countSlices; j++)
476 Orthanc::DicomPath referencedInstancePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, 571 {
477 DICOM_TAG_CONTOUR_SEQUENCE, 0, 572 unsigned int countPoints;
478 DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0, 573
479 DICOM_TAG_REFERENCED_SOP_INSTANCE_UID); 574 countPointsPath.SetPrefixIndex(1, j);
480 575 if (!reader.GetUnsignedIntegerValue(countPoints, countPointsPath))
481 Orthanc::DicomPath contourDataPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, 576 {
482 DICOM_TAG_CONTOUR_SEQUENCE, 0, 577 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
483 DICOM_TAG_CONTOUR_DATA); 578 }
484 579
485 for (size_t j = 0; j < countSlices; j++) 580 //LOG(INFO) << "Parsing slice containing " << countPoints << " vertices";
486 { 581
487 unsigned int countPoints; 582 geometricTypePath.SetPrefixIndex(1, j);
488 583 std::string type = reader.GetMandatoryStringValue(geometricTypePath);
489 countPointsPath.SetPrefixIndex(1, j); 584 if (type != "CLOSED_PLANAR")
490 if (!reader.GetUnsignedIntegerValue(countPoints, countPointsPath)) 585 {
491 { 586 LOG(WARNING) << "Ignoring contour with geometry type: " << type;
492 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); 587 continue;
493 } 588 }
494 589
495 //LOG(INFO) << "Parsing slice containing " << countPoints << " vertices"; 590 size_t size;
496 591
497 geometricTypePath.SetPrefixIndex(1, j); 592 imageSequencePath.SetPrefixIndex(1, j);
498 std::string type = reader.GetMandatoryStringValue(geometricTypePath); 593 if (!tags.GetSequenceSize(size, imageSequencePath) || size != 1)
499 if (type != "CLOSED_PLANAR") 594 {
500 { 595 LOG(ERROR) << "The ContourImageSequence sequence (tag 3006,0016) must be present and contain one entry.";
501 LOG(WARNING) << "Ignoring contour with geometry type: " << type; 596 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
502 continue; 597 }
503 } 598
504 599 referencedInstancePath.SetPrefixIndex(1, j);
505 size_t size; 600 std::string sopInstanceUid = reader.GetMandatoryStringValue(referencedInstancePath);
506 601
507 imageSequencePath.SetPrefixIndex(1, j); 602 contourDataPath.SetPrefixIndex(1, j);
508 if (!tags.GetSequenceSize(size, imageSequencePath) || size != 1) 603 std::string slicesData = reader.GetMandatoryStringValue(contourDataPath);
509 { 604
510 LOG(ERROR) << "The ContourImageSequence sequence (tag 3006,0016) must be present and contain one entry."; 605 Vector points;
511 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); 606
512 } 607 if (!GenericToolbox::FastParseVector(points, slicesData) ||
513 608 points.size() != 3 * countPoints)
514 referencedInstancePath.SetPrefixIndex(1, j); 609 {
515 std::string sopInstanceUid = reader.GetMandatoryStringValue(referencedInstancePath); 610 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
516 611 }
517 contourDataPath.SetPrefixIndex(1, j); 612
518 std::string slicesData = reader.GetMandatoryStringValue(contourDataPath); 613 // seen in real world
519 614 if(Orthanc::Toolbox::StripSpaces(sopInstanceUid) == "")
520 Vector points; 615 {
521 616 LOG(ERROR) << "WARNING. The following Dicom tag (Referenced SOP Instance UID) contains an empty value : // (3006,0039)[" << i << "] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155)";
522 if (!GenericToolbox::FastParseVector(points, slicesData) || 617 }
523 points.size() != 3 * countPoints) 618
524 { 619 Polygon polygon(sopInstanceUid);
525 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); 620 polygon.Reserve(countPoints);
526 } 621
527 622 for (size_t k = 0; k < countPoints; k++)
528 // seen in real world 623 {
529 if(Orthanc::Toolbox::StripSpaces(sopInstanceUid) == "") 624 Vector v(3);
530 { 625 v[0] = points[3 * k];
531 LOG(ERROR) << "WARNING. The following Dicom tag (Referenced SOP Instance UID) contains an empty value : // (3006,0039)[" << i << "] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155)"; 626 v[1] = points[3 * k + 1];
532 } 627 v[2] = points[3 * k + 2];
533 628 polygon.AddPoint(v);
534 Polygon polygon(sopInstanceUid); 629 }
535 polygon.Reserve(countPoints); 630
536 631 target.polygons_.push_back(polygon);
537 for (size_t k = 0; k < countPoints; k++) 632 }
538 {
539 Vector v(3);
540 v[0] = points[3 * k];
541 v[1] = points[3 * k + 1];
542 v[2] = points[3 * k + 2];
543 polygon.AddPoint(v);
544 }
545
546 structures_[i].polygons_.push_back(polygon);
547 } 633 }
548 } 634 }
549 635
550 EstimateGeometry(); 636 EstimateGeometry();
551 637