Mercurial > hg > orthanc-stone
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 |