comparison Samples/Sdl/Loader.cpp @ 625:2eeb5857eb43

DicomInstanceParameters
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 07 May 2019 18:11:52 +0200
parents 42dadae61fa9
children cc69acccd9f8
comparison
equal deleted inserted replaced
624:573e35378999 625:2eeb5857eb43
17 * You should have received a copy of the GNU Affero General Public License 17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/ 19 **/
20 20
21 // From Stone 21 // From Stone
22 #include "../../Framework/Messages/ICallable.h"
23 #include "../../Framework/Messages/IMessage.h"
24 #include "../../Framework/Messages/IObservable.h"
25 #include "../../Framework/Messages/MessageBroker.h"
22 #include "../../Framework/StoneInitialization.h" 26 #include "../../Framework/StoneInitialization.h"
23 #include "../../Framework/Messages/IMessage.h" 27 #include "../../Framework/Toolbox/GeometryToolbox.h"
24 #include "../../Framework/Messages/MessageBroker.h"
25 #include "../../Framework/Messages/ICallable.h"
26 #include "../../Framework/Messages/IObservable.h"
27 #include "../../Framework/Volumes/ImageBuffer3D.h" 28 #include "../../Framework/Volumes/ImageBuffer3D.h"
28 29
29 // From Orthanc framework 30 // From Orthanc framework
31 #include <Core/DicomFormat/DicomArray.h>
32 #include <Core/DicomFormat/DicomImageInformation.h>
33 #include <Core/HttpClient.h>
30 #include <Core/IDynamicObject.h> 34 #include <Core/IDynamicObject.h>
31 #include <Core/Images/Image.h> 35 #include <Core/Images/Image.h>
32 #include <Core/Images/ImageProcessing.h> 36 #include <Core/Images/ImageProcessing.h>
33 #include <Core/Images/PngWriter.h> 37 #include <Core/Images/PngWriter.h>
34 #include <Core/Logging.h> 38 #include <Core/Logging.h>
35 #include <Core/HttpClient.h>
36 #include <Core/MultiThreading/SharedMessageQueue.h> 39 #include <Core/MultiThreading/SharedMessageQueue.h>
37 #include <Core/OrthancException.h> 40 #include <Core/OrthancException.h>
41 #include <Core/Toolbox.h>
38 42
39 #include <json/reader.h> 43 #include <json/reader.h>
40 #include <json/value.h> 44 #include <json/value.h>
41 #include <json/writer.h> 45 #include <json/writer.h>
42 46
152 const std::string& GetAnswer() const 156 const std::string& GetAnswer() const
153 { 157 {
154 return answer_; 158 return answer_;
155 } 159 }
156 160
157 void GetJsonBody(Json::Value& target) const 161 void ParseJsonBody(Json::Value& target) const
158 { 162 {
159 Json::Reader reader; 163 Json::Reader reader;
160 if (!reader.parse(answer_, target)) 164 if (!reader.parse(answer_, target))
161 { 165 {
162 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); 166 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
335 void Execute(const OrthancStone::IObserver& receiver, 339 void Execute(const OrthancStone::IObserver& receiver,
336 const OrthancApiOracleCommand& command) 340 const OrthancApiOracleCommand& command)
337 { 341 {
338 Orthanc::HttpClient client(orthanc_, command.GetUri()); 342 Orthanc::HttpClient client(orthanc_, command.GetUri());
339 client.SetMethod(command.GetMethod()); 343 client.SetMethod(command.GetMethod());
340 client.SetBody(command.GetBody());
341 client.SetTimeout(command.GetTimeout()); 344 client.SetTimeout(command.GetTimeout());
345
346 if (command.GetMethod() == Orthanc::HttpMethod_Post ||
347 command.GetMethod() == Orthanc::HttpMethod_Put)
348 {
349 client.SetBody(command.GetBody());
350 }
342 351
343 { 352 {
344 const HttpHeaders& headers = command.GetHttpHeaders(); 353 const HttpHeaders& headers = command.GetHttpHeaders();
345 for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ ) 354 for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ )
346 { 355 {
381 390
382 if (object.get() != NULL) 391 if (object.get() != NULL)
383 { 392 {
384 const Item& item = dynamic_cast<Item&>(*object); 393 const Item& item = dynamic_cast<Item&>(*object);
385 394
386 switch (item.GetCommand().GetType()) 395 try
387 { 396 {
388 case IOracleCommand::Type_OrthancApi: 397 switch (item.GetCommand().GetType())
389 Execute(item.GetReceiver(), 398 {
390 dynamic_cast<const OrthancApiOracleCommand&>(item.GetCommand())); 399 case IOracleCommand::Type_OrthancApi:
391 break; 400 Execute(item.GetReceiver(),
392 401 dynamic_cast<const OrthancApiOracleCommand&>(item.GetCommand()));
393 default: 402 break;
394 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); 403
404 default:
405 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
406 }
407 }
408 catch (Orthanc::OrthancException& e)
409 {
410 LOG(ERROR) << "Exception within the oracle: " << e.What();
411 }
412 catch (...)
413 {
414 LOG(ERROR) << "Native exception within the oracle";
395 } 415 }
396 } 416 }
397 } 417 }
398 418
399 419
587 }; 607 };
588 }; 608 };
589 609
590 610
591 611
612 class DicomInstanceParameters : public boost::noncopyable
613 {
614 private:
615 Orthanc::DicomImageInformation information_;
616 OrthancStone::SopClassUid sopClassUid_;
617 double thickness_;
618 double pixelSpacingX_;
619 double pixelSpacingY_;
620 OrthancStone::CoordinateSystem3D geometry_;
621 OrthancStone::Vector frameOffsets_;
622 bool isColor_;
623 bool hasRescale_;
624 double rescaleOffset_;
625 double rescaleSlope_;
626 bool hasDefaultWindowing_;
627 float defaultWindowingCenter_;
628 float defaultWindowingWidth_;
629
630 void ComputeDoseOffsets(const Orthanc::DicomMap& dicom)
631 {
632 // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html
633
634 {
635 std::string increment;
636
637 if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false))
638 {
639 Orthanc::Toolbox::ToUpperCase(increment);
640 if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag
641 {
642 LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag";
643 return;
644 }
645 }
646 }
647
648 if (!OrthancStone::LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) ||
649 frameOffsets_.size() < information_.GetNumberOfFrames())
650 {
651 LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)";
652 frameOffsets_.clear();
653 }
654 else
655 {
656 if (frameOffsets_.size() >= 2)
657 {
658 thickness_ = frameOffsets_[1] - frameOffsets_[0];
659
660 if (thickness_ < 0)
661 {
662 thickness_ = -thickness_;
663 }
664 }
665 }
666 }
667
668 public:
669 DicomInstanceParameters(const Orthanc::DicomMap& dicom) :
670 information_(dicom)
671 {
672 if (information_.GetNumberOfFrames() <= 0)
673 {
674 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
675 }
676
677 std::string s;
678 if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
679 {
680 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
681 }
682 else
683 {
684 sopClassUid_ = OrthancStone::StringToSopClassUid(s);
685 }
686
687 if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS))
688 {
689 thickness_ = 100.0 * std::numeric_limits<double>::epsilon();
690 }
691
692 OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom);
693
694 std::string position, orientation;
695 if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) &&
696 dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false))
697 {
698 geometry_ = OrthancStone::CoordinateSystem3D(position, orientation);
699 }
700
701 if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
702 {
703 ComputeDoseOffsets(dicom);
704 }
705
706 isColor_ = (information_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 &&
707 information_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2);
708
709 double doseGridScaling;
710
711 if (dicom.ParseDouble(rescaleOffset_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
712 dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE))
713 {
714 hasRescale_ = true;
715 }
716 else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
717 {
718 hasRescale_ = true;
719 rescaleOffset_ = 0;
720 rescaleSlope_ = doseGridScaling;
721 }
722 else
723 {
724 hasRescale_ = false;
725 }
726
727 OrthancStone::Vector c, w;
728 if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
729 OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
730 c.size() > 0 &&
731 w.size() > 0)
732 {
733 hasDefaultWindowing_ = true;
734 defaultWindowingCenter_ = static_cast<float>(c[0]);
735 defaultWindowingWidth_ = static_cast<float>(w[0]);
736 }
737 else
738 {
739 hasDefaultWindowing_ = false;
740 }
741 }
742
743 const Orthanc::DicomImageInformation& GetImageInformation() const
744 {
745 return information_;
746 }
747
748 OrthancStone::SopClassUid GetSopClassUid() const
749 {
750 return sopClassUid_;
751 }
752
753 double GetThickness() const
754 {
755 return thickness_;
756 }
757
758 double GetPixelSpacingX() const
759 {
760 return pixelSpacingX_;
761 }
762
763 double GetPixelSpacingY() const
764 {
765 return pixelSpacingY_;
766 }
767
768 const OrthancStone::CoordinateSystem3D& GetGeometry() const
769 {
770 return geometry_;
771 }
772
773 OrthancStone::CoordinateSystem3D GetFrameGeometry(unsigned int frame) const
774 {
775 if (frame >= information_.GetNumberOfFrames())
776 {
777 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
778 }
779
780 if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
781 {
782 if (frame >= frameOffsets_.size())
783 {
784 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
785 }
786
787 return OrthancStone::CoordinateSystem3D(
788 geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(),
789 geometry_.GetAxisX(),
790 geometry_.GetAxisY());
791 }
792 }
793
794 bool FrameContainsPlane(unsigned int frame,
795 const OrthancStone::CoordinateSystem3D& plane) const
796 {
797 if (frame >= information_.GetNumberOfFrames())
798 {
799 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
800 }
801
802 OrthancStone::CoordinateSystem3D tmp = geometry_;
803
804 if (frame != 0)
805 {
806 tmp = GetFrameGeometry(frame);
807 }
808
809 bool opposite; // Ignored
810 return (OrthancStone::GeometryToolbox::IsParallelOrOpposite(
811 opposite, tmp.GetNormal(), plane.GetNormal()) &&
812 OrthancStone::LinearAlgebra::IsNear(
813 tmp.ProjectAlongNormal(tmp.GetOrigin()),
814 tmp.ProjectAlongNormal(plane.GetOrigin()),
815 thickness_ / 2.0));
816 }
817
818 bool IsColor() const
819 {
820 return isColor_;
821 }
822
823 bool HasRescale() const
824 {
825 return hasRescale_;
826 }
827
828 double GetRescaleOffset() const
829 {
830 if (hasRescale_)
831 {
832 return rescaleOffset_;
833 }
834 else
835 {
836 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
837 }
838 }
839
840 double GetRescaleSlope() const
841 {
842 if (hasRescale_)
843 {
844 return rescaleSlope_;
845 }
846 else
847 {
848 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
849 }
850 }
851
852 bool HasDefaultWindowing() const
853 {
854 return hasDefaultWindowing_;
855 }
856
857 float GetDefaultWindowingCenter() const
858 {
859 if (hasDefaultWindowing_)
860 {
861 return defaultWindowingCenter_;
862 }
863 else
864 {
865 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
866 }
867 }
868
869 float GetDefaultWindowingWidth() const
870 {
871 if (hasDefaultWindowing_)
872 {
873 return defaultWindowingWidth_;
874 }
875 else
876 {
877 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
878 }
879 }
880 };
881
882
592 class AxialVolumeOrthancLoader : public OrthancStone::IObserver 883 class AxialVolumeOrthancLoader : public OrthancStone::IObserver
593 { 884 {
594 private: 885 private:
595 void Handle(const Refactoring::OrthancApiOracleCommand::SuccessMessage& message) 886 class MessageHandler : public Orthanc::IDynamicObject
596 { 887 {
597 Json::Value v; 888 public:
598 message.GetJsonBody(v); 889 virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const = 0;
599 890 };
600 printf("ICI [%s]\n", v.toStyledString().c_str()); 891
601 } 892 void Handle(const OrthancApiOracleCommand::SuccessMessage& message)
602 893 {
603 894 dynamic_cast<const MessageHandler&>(message.GetOrigin().GetPayload()).Handle(message);
895 }
896
897
898 class LoadSeriesGeometryHandler : public MessageHandler
899 {
900 private:
901 AxialVolumeOrthancLoader& that_;
902
903 public:
904 LoadSeriesGeometryHandler(AxialVolumeOrthancLoader& that) :
905 that_(that)
906 {
907 }
908
909 virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const
910 {
911 Json::Value value;
912 message.ParseJsonBody(value);
913
914 if (value.type() != Json::objectValue)
915 {
916 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
917 }
918
919 Json::Value::Members instances = value.getMemberNames();
920
921 for (size_t i = 0; i < instances.size(); i++)
922 {
923 Orthanc::DicomMap dicom;
924 dicom.FromDicomAsJson(value[instances[i]]);
925
926 DicomInstanceParameters instance(dicom);
927 }
928 }
929 };
930
931
932 class LoadInstanceGeometryHandler : public MessageHandler
933 {
934 private:
935 AxialVolumeOrthancLoader& that_;
936
937 public:
938 LoadInstanceGeometryHandler(AxialVolumeOrthancLoader& that) :
939 that_(that)
940 {
941 }
942
943 virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const
944 {
945 Json::Value value;
946 message.ParseJsonBody(value);
947
948 if (value.type() != Json::objectValue)
949 {
950 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
951 }
952
953 Orthanc::DicomMap dicom;
954 dicom.FromDicomAsJson(value);
955
956 DicomInstanceParameters instance(dicom);
957 }
958 };
959
960
961 bool active_;
604 std::auto_ptr<OrthancStone::ImageBuffer3D> image_; 962 std::auto_ptr<OrthancStone::ImageBuffer3D> image_;
605 963
606 964
607 public: 965 public:
608 AxialVolumeOrthancLoader(OrthancStone::IObservable& oracle) : 966 AxialVolumeOrthancLoader(OrthancStone::IObservable& oracle) :
609 IObserver(oracle.GetBroker()) 967 IObserver(oracle.GetBroker()),
968 active_(false)
610 { 969 {
611 oracle.RegisterObserverCallback( 970 oracle.RegisterObserverCallback(
612 new OrthancStone::Callable<AxialVolumeOrthancLoader, OrthancApiOracleCommand::SuccessMessage> 971 new OrthancStone::Callable<AxialVolumeOrthancLoader, OrthancApiOracleCommand::SuccessMessage>
613 (*this, &AxialVolumeOrthancLoader::Handle)); 972 (*this, &AxialVolumeOrthancLoader::Handle));
973 }
974
975 void LoadSeries(IOracle& oracle,
976 const std::string& seriesId)
977 {
978 if (active_)
979 {
980 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
981 }
982
983 active_ = true;
984
985 std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand);
986 command->SetUri("/series/" + seriesId + "/instances-tags");
987 command->SetPayload(new LoadSeriesGeometryHandler(*this));
988
989 oracle.Schedule(*this, command.release());
990 }
991
992 void LoadInstance(IOracle& oracle,
993 const std::string& instanceId)
994 {
995 if (active_)
996 {
997 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
998 }
999
1000 active_ = true;
1001
1002 // Tag "3004-000c" is "Grid Frame Offset Vector", which is
1003 // mandatory to read RT DOSE, but is too long to be returned by default
1004
1005 std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand);
1006 command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3004-000c");
1007 command->SetPayload(new LoadInstanceGeometryHandler(*this));
1008
1009 oracle.Schedule(*this, command.release());
614 } 1010 }
615 }; 1011 };
616 1012
617 } 1013 }
618 1014
622 { 1018 {
623 private: 1019 private:
624 void Handle(const Refactoring::OrthancApiOracleCommand::SuccessMessage& message) 1020 void Handle(const Refactoring::OrthancApiOracleCommand::SuccessMessage& message)
625 { 1021 {
626 Json::Value v; 1022 Json::Value v;
627 message.GetJsonBody(v); 1023 message.ParseJsonBody(v);
628 1024
629 printf("ICI [%s]\n", v.toStyledString().c_str()); 1025 printf("ICI [%s]\n", v.toStyledString().c_str());
630 } 1026 }
631 1027
632 void Handle(const Refactoring::OrthancApiOracleCommand::FailureMessage& message) 1028 void Handle(const Refactoring::OrthancApiOracleCommand::FailureMessage& message)
646 1042
647 1043
648 void Run(Refactoring::NativeApplicationContext& context) 1044 void Run(Refactoring::NativeApplicationContext& context)
649 { 1045 {
650 std::auto_ptr<Toto> toto; 1046 std::auto_ptr<Toto> toto;
1047 std::auto_ptr<Refactoring::AxialVolumeOrthancLoader> loader1, loader2;
651 1048
652 { 1049 {
653 Refactoring::NativeApplicationContext::WriterLock lock(context); 1050 Refactoring::NativeApplicationContext::WriterLock lock(context);
654 toto.reset(new Toto(lock.GetOracleObservable())); 1051 toto.reset(new Toto(lock.GetOracleObservable()));
655 } 1052 loader1.reset(new Refactoring::AxialVolumeOrthancLoader(lock.GetOracleObservable()));
656 1053 loader2.reset(new Refactoring::AxialVolumeOrthancLoader(lock.GetOracleObservable()));
657 std::auto_ptr<Refactoring::AxialVolumeOrthancLoader> loader;
658
659 {
660 Refactoring::NativeApplicationContext::WriterLock lock(context);
661 loader.reset(new Refactoring::AxialVolumeOrthancLoader(lock.GetOracleObservable()));
662 } 1054 }
663 1055
664 Refactoring::NativeOracle oracle(context); 1056 Refactoring::NativeOracle oracle(context);
665 1057
666 { 1058 {
682 command->SetUri("/tools/find"); 1074 command->SetUri("/tools/find");
683 command->SetBody(v); 1075 command->SetBody(v);
684 1076
685 oracle.Schedule(*toto, command.release()); 1077 oracle.Schedule(*toto, command.release());
686 } 1078 }
1079
1080 // 2017-11-17-Anonymized
1081 loader1->LoadSeries(oracle, "cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT
1082 loader2->LoadInstance(oracle, "41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE
687 1083
688 boost::this_thread::sleep(boost::posix_time::seconds(1)); 1084 boost::this_thread::sleep(boost::posix_time::seconds(1));
689 1085
690 oracle.Stop(); 1086 oracle.Stop();
691 } 1087 }