comparison OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp @ 4479:1619cffd1948

"/tools/create-dicom": New flag "Force" to bypass consistency checks for the DICOM tags
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 27 Jan 2021 18:37:34 +0100
parents d9473bd5ed43
children 8f9090b137f1
comparison
equal deleted inserted replaced
4478:5248be65146a 4479:1619cffd1948
451 451
452 452
453 static void InjectTags(ParsedDicomFile& dicom, 453 static void InjectTags(ParsedDicomFile& dicom,
454 const Json::Value& tags, 454 const Json::Value& tags,
455 bool decodeBinaryTags, 455 bool decodeBinaryTags,
456 const std::string& privateCreator) 456 const std::string& privateCreator,
457 bool force)
457 { 458 {
458 if (tags.type() != Json::objectValue) 459 if (tags.type() != Json::objectValue)
459 { 460 {
460 throw OrthancException(ErrorCode_BadRequest, "Tags field is not an array"); 461 throw OrthancException(ErrorCode_BadRequest, "Tags field is not an array");
461 } 462 }
467 const std::string& name = members[i]; 468 const std::string& name = members[i];
468 DicomTag tag = FromDcmtkBridge::ParseTag(name); 469 DicomTag tag = FromDcmtkBridge::ParseTag(name);
469 470
470 if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) 471 if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET)
471 { 472 {
472 if (tag != DICOM_TAG_PATIENT_ID && 473 if (!force &&
474 tag != DICOM_TAG_PATIENT_ID &&
473 tag != DICOM_TAG_ACQUISITION_DATE && 475 tag != DICOM_TAG_ACQUISITION_DATE &&
474 tag != DICOM_TAG_ACQUISITION_TIME && 476 tag != DICOM_TAG_ACQUISITION_TIME &&
475 tag != DICOM_TAG_CONTENT_DATE && 477 tag != DICOM_TAG_CONTENT_DATE &&
476 tag != DICOM_TAG_CONTENT_TIME && 478 tag != DICOM_TAG_CONTENT_TIME &&
477 tag != DICOM_TAG_INSTANCE_CREATION_DATE && 479 tag != DICOM_TAG_INSTANCE_CREATION_DATE &&
500 502
501 static void CreateSeries(RestApiPostCall& call, 503 static void CreateSeries(RestApiPostCall& call,
502 ParsedDicomFile& base /* in */, 504 ParsedDicomFile& base /* in */,
503 const Json::Value& content, 505 const Json::Value& content,
504 bool decodeBinaryTags, 506 bool decodeBinaryTags,
505 const std::string& privateCreator) 507 const std::string& privateCreator,
508 bool force)
506 { 509 {
507 assert(content.isArray()); 510 assert(content.isArray());
508 assert(content.size() > 0); 511 assert(content.size() > 0);
509 ServerContext& context = OrthancRestApi::GetContext(call); 512 ServerContext& context = OrthancRestApi::GetContext(call);
510 513
533 536
534 payload = &content[i]["Content"]; 537 payload = &content[i]["Content"];
535 538
536 if (content[i].isMember("Tags")) 539 if (content[i].isMember("Tags"))
537 { 540 {
538 InjectTags(*dicom, content[i]["Tags"], decodeBinaryTags, privateCreator); 541 InjectTags(*dicom, content[i]["Tags"], decodeBinaryTags, privateCreator, force);
539 } 542 }
540 } 543 }
541 544
542 if (payload == NULL || 545 if (payload == NULL ||
543 payload->type() != Json::stringValue) 546 payload->type() != Json::stringValue)
575 578
576 579
577 static void CreateDicomV2(RestApiPostCall& call, 580 static void CreateDicomV2(RestApiPostCall& call,
578 const Json::Value& request) 581 const Json::Value& request)
579 { 582 {
583 static const char* const CONTENT = "Content";
584 static const char* const FORCE = "Force";
585 static const char* const INTERPRET_BINARY_TAGS = "InterpretBinaryTags";
586 static const char* const PARENT = "Parent";
587 static const char* const PRIVATE_CREATOR = "PrivateCreator";
588 static const char* const SPECIFIC_CHARACTER_SET_2 = "SpecificCharacterSet";
589 static const char* const TAGS = "Tags";
590 static const char* const TYPE = "Type";
591 static const char* const VALUE = "Value";
592
580 assert(request.isObject()); 593 assert(request.isObject());
581 ServerContext& context = OrthancRestApi::GetContext(call); 594 ServerContext& context = OrthancRestApi::GetContext(call);
582 595
583 if (!request.isMember("Tags") || 596 if (!request.isMember(TAGS) ||
584 request["Tags"].type() != Json::objectValue) 597 request[TAGS].type() != Json::objectValue)
585 { 598 {
586 throw OrthancException(ErrorCode_BadRequest); 599 throw OrthancException(ErrorCode_BadRequest);
587 } 600 }
588 601
589 ParsedDicomFile dicom(true); 602 ParsedDicomFile dicom(true);
590 603
591 { 604 {
592 Encoding encoding; 605 Encoding encoding;
593 606
594 if (request["Tags"].isMember("SpecificCharacterSet")) 607 if (request[TAGS].isMember(SPECIFIC_CHARACTER_SET_2))
595 { 608 {
596 const char* tmp = request["Tags"]["SpecificCharacterSet"].asCString(); 609 const char* tmp = request[TAGS][SPECIFIC_CHARACTER_SET_2].asCString();
597 if (!GetDicomEncoding(encoding, tmp)) 610 if (!GetDicomEncoding(encoding, tmp))
598 { 611 {
599 throw OrthancException(ErrorCode_ParameterOutOfRange, 612 throw OrthancException(ErrorCode_ParameterOutOfRange,
600 "Unknown specific character set: " + std::string(tmp)); 613 "Unknown specific character set: " + std::string(tmp));
601 } 614 }
608 dicom.SetEncoding(encoding); 621 dicom.SetEncoding(encoding);
609 } 622 }
610 623
611 ResourceType parentType = ResourceType_Instance; 624 ResourceType parentType = ResourceType_Instance;
612 625
613 if (request.isMember("Parent")) 626 if (request.isMember(PARENT))
614 { 627 {
615 // Locate the parent tags 628 // Locate the parent tags
616 std::string parent = request["Parent"].asString(); 629 std::string parent = request[PARENT].asString();
617 if (!context.GetIndex().LookupResourceType(parentType, parent)) 630 if (!context.GetIndex().LookupResourceType(parentType, parent))
618 { 631 {
619 throw OrthancException(ErrorCode_CreateDicomBadParent); 632 throw OrthancException(ErrorCode_CreateDicomBadParent);
620 } 633 }
621 634
651 664
652 if (siblingTags.isMember(SPECIFIC_CHARACTER_SET)) 665 if (siblingTags.isMember(SPECIFIC_CHARACTER_SET))
653 { 666 {
654 Encoding encoding; 667 Encoding encoding;
655 668
656 if (!siblingTags[SPECIFIC_CHARACTER_SET].isMember("Value") || 669 if (!siblingTags[SPECIFIC_CHARACTER_SET].isMember(VALUE) ||
657 siblingTags[SPECIFIC_CHARACTER_SET]["Value"].type() != Json::stringValue || 670 siblingTags[SPECIFIC_CHARACTER_SET][VALUE].type() != Json::stringValue ||
658 !GetDicomEncoding(encoding, siblingTags[SPECIFIC_CHARACTER_SET]["Value"].asCString())) 671 !GetDicomEncoding(encoding, siblingTags[SPECIFIC_CHARACTER_SET][VALUE].asCString()))
659 { 672 {
660 LOG(WARNING) << "Instance with an incorrect Specific Character Set, " 673 LOG(WARNING) << "Instance with an incorrect Specific Character Set, "
661 << "using the default Orthanc encoding: " << siblingInstanceId; 674 << "using the default Orthanc encoding: " << siblingInstanceId;
662 encoding = GetDefaultDicomEncoding(); 675 encoding = GetDefaultDicomEncoding();
663 } 676 }
697 { 710 {
698 std::string t = it->Format(); 711 std::string t = it->Format();
699 if (siblingTags.isMember(t)) 712 if (siblingTags.isMember(t))
700 { 713 {
701 const Json::Value& tag = siblingTags[t]; 714 const Json::Value& tag = siblingTags[t];
702 if (tag["Type"] == "Null") 715 if (tag[TYPE] == "Null")
703 { 716 {
704 dicom.ReplacePlainString(*it, ""); 717 dicom.ReplacePlainString(*it, "");
705 } 718 }
706 else if (tag["Type"] == "String") 719 else if (tag[TYPE] == "String")
707 { 720 {
708 std::string value = tag["Value"].asString(); // This is an UTF-8 value (as it comes from JSON) 721 std::string value = tag[VALUE].asString(); // This is an UTF-8 value (as it comes from JSON)
709 dicom.ReplacePlainString(*it, value); 722 dicom.ReplacePlainString(*it, value);
710 } 723 }
711 } 724 }
712 } 725 }
713 } 726 }
714 727
715 728
716 bool decodeBinaryTags = true; 729 bool decodeBinaryTags = true;
717 if (request.isMember("InterpretBinaryTags")) 730 if (request.isMember(INTERPRET_BINARY_TAGS))
718 { 731 {
719 const Json::Value& v = request["InterpretBinaryTags"]; 732 const Json::Value& v = request[INTERPRET_BINARY_TAGS];
720 if (v.type() != Json::booleanValue) 733 if (v.type() != Json::booleanValue)
721 { 734 {
722 throw OrthancException(ErrorCode_BadRequest); 735 throw OrthancException(ErrorCode_BadRequest);
723 } 736 }
724 737
726 } 739 }
727 740
728 741
729 // New argument in Orthanc 1.6.0 742 // New argument in Orthanc 1.6.0
730 std::string privateCreator; 743 std::string privateCreator;
731 if (request.isMember("PrivateCreator")) 744 if (request.isMember(PRIVATE_CREATOR))
732 { 745 {
733 const Json::Value& v = request["PrivateCreator"]; 746 const Json::Value& v = request[PRIVATE_CREATOR];
734 if (v.type() != Json::stringValue) 747 if (v.type() != Json::stringValue)
735 { 748 {
736 throw OrthancException(ErrorCode_BadRequest); 749 throw OrthancException(ErrorCode_BadRequest);
737 } 750 }
738 751
742 { 755 {
743 OrthancConfiguration::ReaderLock lock; 756 OrthancConfiguration::ReaderLock lock;
744 privateCreator = lock.GetConfiguration().GetDefaultPrivateCreator(); 757 privateCreator = lock.GetConfiguration().GetDefaultPrivateCreator();
745 } 758 }
746 759
747 760
761 // New in Orthanc 1.9.0
762 bool force = false;
763 if (request.isMember(FORCE))
764 {
765 const Json::Value& v = request[FORCE];
766 if (v.type() != Json::booleanValue)
767 {
768 throw OrthancException(ErrorCode_BadRequest);
769 }
770
771 force = v.asBool();
772 }
773
774
748 // Inject time-related information 775 // Inject time-related information
749 std::string date, time; 776 std::string date, time;
750 SystemToolbox::GetNowDicom(date, time, true /* use UTC time (not local time) */); 777 SystemToolbox::GetNowDicom(date, time, true /* use UTC time (not local time) */);
751 dicom.ReplacePlainString(DICOM_TAG_ACQUISITION_DATE, date); 778 dicom.ReplacePlainString(DICOM_TAG_ACQUISITION_DATE, date);
752 dicom.ReplacePlainString(DICOM_TAG_ACQUISITION_TIME, time); 779 dicom.ReplacePlainString(DICOM_TAG_ACQUISITION_TIME, time);
769 dicom.ReplacePlainString(DICOM_TAG_STUDY_DATE, date); 796 dicom.ReplacePlainString(DICOM_TAG_STUDY_DATE, date);
770 dicom.ReplacePlainString(DICOM_TAG_STUDY_TIME, time); 797 dicom.ReplacePlainString(DICOM_TAG_STUDY_TIME, time);
771 } 798 }
772 799
773 800
774 InjectTags(dicom, request["Tags"], decodeBinaryTags, privateCreator); 801 InjectTags(dicom, request[TAGS], decodeBinaryTags, privateCreator, force);
775 802
776 803
777 // Inject the content (either an image, or a PDF file) 804 // Inject the content (either an image, or a PDF file)
778 if (request.isMember("Content")) 805 if (request.isMember(CONTENT))
779 { 806 {
780 const Json::Value& content = request["Content"]; 807 const Json::Value& content = request[CONTENT];
781 808
782 if (content.type() == Json::stringValue) 809 if (content.type() == Json::stringValue)
783 { 810 {
784 dicom.EmbedContent(request["Content"].asString()); 811 dicom.EmbedContent(request[CONTENT].asString());
785 812
786 } 813 }
787 else if (content.type() == Json::arrayValue) 814 else if (content.type() == Json::arrayValue)
788 { 815 {
789 if (content.size() > 0) 816 if (content.size() > 0)
790 { 817 {
791 // Let's create a series instead of a single instance 818 // Let's create a series instead of a single instance
792 CreateSeries(call, dicom, content, decodeBinaryTags, privateCreator); 819 CreateSeries(call, dicom, content, decodeBinaryTags, privateCreator, force);
793 return; 820 return;
794 } 821 }
795 } 822 }
796 else 823 else
797 { 824 {
828 "If some value in the `Tags` associative array is formatted according to some " 855 "If some value in the `Tags` associative array is formatted according to some "
829 "[data URI scheme encoding](https://en.wikipedia.org/wiki/Data_URI_scheme), " 856 "[data URI scheme encoding](https://en.wikipedia.org/wiki/Data_URI_scheme), "
830 "whether this value is decoded to a binary value or kept as such (`true` by default)", false) 857 "whether this value is decoded to a binary value or kept as such (`true` by default)", false)
831 .SetRequestField("PrivateCreator", RestApiCallDocumentation::Type_String, 858 .SetRequestField("PrivateCreator", RestApiCallDocumentation::Type_String,
832 "The private creator to be used for private tags in `Tags`", false) 859 "The private creator to be used for private tags in `Tags`", false)
860 .SetRequestField("Force", RestApiCallDocumentation::Type_Boolean,
861 "Avoid the consistency checks for the DICOM tags that enforce the DICOM model of the real-world. "
862 "You can notably use this flag if you need to manually set the tags `StudyInstanceUID`, "
863 "`SeriesInstanceUID`, or `SOPInstanceUID`. Be careful with this feature.", false)
833 .SetAnswerField("ID", RestApiCallDocumentation::Type_String, "Orthanc identifier of the newly created instance") 864 .SetAnswerField("ID", RestApiCallDocumentation::Type_String, "Orthanc identifier of the newly created instance")
834 .SetAnswerField("Path", RestApiCallDocumentation::Type_String, "Path to access the instance in the REST API"); 865 .SetAnswerField("Path", RestApiCallDocumentation::Type_String, "Path to access the instance in the REST API");
835 return; 866 return;
836 } 867 }
837 868