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