Mercurial > hg > orthanc
comparison OrthancServer/OrthancRestApi/OrthancRestResources.cpp @ 3683:12253ddefe5a
skeleton for new route: /instances/{id}/rendered
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 24 Feb 2020 17:19:37 +0100 |
parents | 94f4a18a79cc |
children | 2cc34837d694 |
comparison
equal
deleted
inserted
replaced
3682:5f64c866108a | 3683:12253ddefe5a |
---|---|
33 | 33 |
34 #include "../PrecompiledHeadersServer.h" | 34 #include "../PrecompiledHeadersServer.h" |
35 #include "OrthancRestApi.h" | 35 #include "OrthancRestApi.h" |
36 | 36 |
37 #include "../../Core/Compression/GzipCompressor.h" | 37 #include "../../Core/Compression/GzipCompressor.h" |
38 #include "../../Core/DicomFormat/DicomImageInformation.h" | |
38 #include "../../Core/DicomParsing/DicomWebJsonVisitor.h" | 39 #include "../../Core/DicomParsing/DicomWebJsonVisitor.h" |
39 #include "../../Core/DicomParsing/FromDcmtkBridge.h" | 40 #include "../../Core/DicomParsing/FromDcmtkBridge.h" |
40 #include "../../Core/DicomParsing/Internals/DicomImageDecoder.h" | 41 #include "../../Core/DicomParsing/Internals/DicomImageDecoder.h" |
41 #include "../../Core/HttpServer/HttpContentNegociation.h" | 42 #include "../../Core/HttpServer/HttpContentNegociation.h" |
42 #include "../../Core/Images/ImageProcessing.h" | |
43 #include "../../Core/Logging.h" | 43 #include "../../Core/Logging.h" |
44 #include "../DefaultDicomImageDecoder.h" | 44 #include "../DefaultDicomImageDecoder.h" |
45 #include "../OrthancConfiguration.h" | 45 #include "../OrthancConfiguration.h" |
46 #include "../Search/DatabaseLookup.h" | 46 #include "../Search/DatabaseLookup.h" |
47 #include "../ServerContext.h" | 47 #include "../ServerContext.h" |
501 } | 501 } |
502 }; | 502 }; |
503 } | 503 } |
504 | 504 |
505 | 505 |
506 void LookupWindowingTags(const ParsedDicomFile& dicom, float& windowCenter, float& windowWidth, float& rescaleSlope, float& rescaleIntercept, bool& invert) | 506 namespace |
507 { | 507 { |
508 DicomMap dicomTags; | 508 class IDecodedFrameHandler : public boost::noncopyable |
509 dicom.ExtractDicomSummary(dicomTags); | 509 { |
510 | 510 public: |
511 | 511 virtual ~IDecodedFrameHandler() |
512 unsigned int bitsStored = boost::lexical_cast<unsigned int>(dicomTags.GetStringValue(Orthanc::DICOM_TAG_BITS_STORED, "8", false)); | 512 { |
513 windowWidth = static_cast<float>(2 << (bitsStored - 1)); | 513 } |
514 windowCenter = windowWidth / 2; | 514 |
515 rescaleSlope = 1.0f; | 515 virtual void Handle(RestApiGetCall& call, |
516 rescaleIntercept = 0.0f; | 516 std::auto_ptr<ImageAccessor>& decoded, |
517 invert = false; | 517 const DicomMap& dicom) = 0; |
518 | 518 |
519 if (dicomTags.HasTag(Orthanc::DICOM_TAG_WINDOW_CENTER) && dicomTags.HasTag(Orthanc::DICOM_TAG_WINDOW_WIDTH)) | 519 virtual bool RequiresDicomTags() const = 0; |
520 { | 520 |
521 dicomTags.ParseFloat(windowCenter, Orthanc::DICOM_TAG_WINDOW_CENTER); | 521 static void Apply(RestApiGetCall& call, |
522 dicomTags.ParseFloat(windowWidth, Orthanc::DICOM_TAG_WINDOW_WIDTH); | 522 IDecodedFrameHandler& handler) |
523 } | 523 { |
524 | 524 ServerContext& context = OrthancRestApi::GetContext(call); |
525 if (dicomTags.HasTag(Orthanc::DICOM_TAG_RESCALE_SLOPE) && dicomTags.HasTag(Orthanc::DICOM_TAG_RESCALE_INTERCEPT)) | 525 |
526 { | 526 std::string frameId = call.GetUriComponent("frame", "0"); |
527 dicomTags.ParseFloat(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE); | 527 |
528 dicomTags.ParseFloat(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT); | 528 unsigned int frame; |
529 } | 529 try |
530 | 530 { |
531 PhotometricInterpretation photometric; | 531 frame = boost::lexical_cast<unsigned int>(frameId); |
532 if (dicom.LookupPhotometricInterpretation(photometric)) | 532 } |
533 { | 533 catch (boost::bad_lexical_cast&) |
534 invert = (photometric == PhotometricInterpretation_Monochrome1); | 534 { |
535 } | 535 return; |
536 } | 536 } |
537 | |
538 DicomMap dicom; | |
539 std::auto_ptr<ImageAccessor> decoded; | |
540 | |
541 try | |
542 { | |
543 std::string publicId = call.GetUriComponent("id", ""); | |
544 | |
545 #if ORTHANC_ENABLE_PLUGINS == 1 | |
546 if (context.GetPlugins().HasCustomImageDecoder()) | |
547 { | |
548 // TODO create a cache of file | |
549 std::string dicomContent; | |
550 context.ReadDicom(dicomContent, publicId); | |
551 decoded.reset(context.GetPlugins().DecodeUnsafe(dicomContent.c_str(), dicomContent.size(), frame)); | |
552 | |
553 /** | |
554 * Note that we call "DecodeUnsafe()": We do not fallback to | |
555 * the builtin decoder if no installed decoder plugin is able | |
556 * to decode the image. This allows us to take advantage of | |
557 * the cache below. | |
558 **/ | |
559 | |
560 if (handler.RequiresDicomTags() && | |
561 decoded.get() != NULL) | |
562 { | |
563 // TODO Optimize this lookup for photometric interpretation: | |
564 // It should be implemented by the plugin to avoid parsing | |
565 // twice the DICOM file | |
566 ParsedDicomFile parsed(dicomContent); | |
567 parsed.ExtractDicomSummary(dicom); | |
568 } | |
569 } | |
570 #endif | |
571 | |
572 if (decoded.get() == NULL) | |
573 { | |
574 // Use Orthanc's built-in decoder, using the cache to speed-up | |
575 // things on multi-frame images | |
576 ServerContext::DicomCacheLocker locker(context, publicId); | |
577 decoded.reset(DicomImageDecoder::Decode(locker.GetDicom(), frame)); | |
578 | |
579 if (handler.RequiresDicomTags()) | |
580 { | |
581 locker.GetDicom().ExtractDicomSummary(dicom); | |
582 } | |
583 } | |
584 } | |
585 catch (OrthancException& e) | |
586 { | |
587 if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange || | |
588 e.GetErrorCode() == ErrorCode_UnknownResource) | |
589 { | |
590 // The frame number is out of the range for this DICOM | |
591 // instance, the resource is not existent | |
592 } | |
593 else | |
594 { | |
595 std::string root = ""; | |
596 for (size_t i = 1; i < call.GetFullUri().size(); i++) | |
597 { | |
598 root += "../"; | |
599 } | |
600 | |
601 call.GetOutput().Redirect(root + "app/images/unsupported.png"); | |
602 } | |
603 return; | |
604 } | |
605 | |
606 handler.Handle(call, decoded, dicom); | |
607 } | |
608 }; | |
609 | |
610 | |
611 class GetImageHandler : public IDecodedFrameHandler | |
612 { | |
613 private: | |
614 ImageExtractionMode mode_; | |
615 | |
616 public: | |
617 GetImageHandler(ImageExtractionMode mode) : | |
618 mode_(mode) | |
619 { | |
620 } | |
621 | |
622 virtual void Handle(RestApiGetCall& call, | |
623 std::auto_ptr<ImageAccessor>& decoded, | |
624 const DicomMap& dicom) ORTHANC_OVERRIDE | |
625 { | |
626 bool invert = false; | |
627 | |
628 if (mode_ == ImageExtractionMode_Preview) | |
629 { | |
630 DicomImageInformation info(dicom); | |
631 invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1); | |
632 } | |
633 | |
634 ImageToEncode image(decoded, mode_, invert); | |
635 | |
636 HttpContentNegociation negociation; | |
637 EncodePng png(image); | |
638 negociation.Register(MIME_PNG, png); | |
639 | |
640 EncodeJpeg jpeg(image, call); | |
641 negociation.Register(MIME_JPEG, jpeg); | |
642 | |
643 EncodePam pam(image); | |
644 negociation.Register(MIME_PAM, pam); | |
645 | |
646 if (negociation.Apply(call.GetHttpHeaders())) | |
647 { | |
648 image.Answer(call.GetOutput()); | |
649 } | |
650 } | |
651 | |
652 virtual bool RequiresDicomTags() const ORTHANC_OVERRIDE | |
653 { | |
654 return mode_ == ImageExtractionMode_Preview; | |
655 } | |
656 }; | |
657 | |
658 | |
659 class RenderedFrameHandler : public IDecodedFrameHandler | |
660 { | |
661 private: | |
662 static void LookupWindowingTags(const DicomMap& dicom, | |
663 float& windowCenter, | |
664 float& windowWidth, | |
665 float& rescaleSlope, | |
666 float& rescaleIntercept, | |
667 bool& invert) | |
668 { | |
669 DicomImageInformation info(dicom); | |
670 | |
671 windowWidth = static_cast<float>(1 << info.GetBitsStored()); | |
672 windowCenter = windowWidth / 2.0f; | |
673 rescaleSlope = 1.0f; | |
674 rescaleIntercept = 0.0f; | |
675 invert = false; | |
676 | |
677 if (dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_CENTER) && | |
678 dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_WIDTH)) | |
679 { | |
680 dicom.ParseFloat(windowCenter, Orthanc::DICOM_TAG_WINDOW_CENTER); | |
681 dicom.ParseFloat(windowWidth, Orthanc::DICOM_TAG_WINDOW_WIDTH); | |
682 } | |
683 | |
684 if (dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_SLOPE) && | |
685 dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_INTERCEPT)) | |
686 { | |
687 dicom.ParseFloat(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE); | |
688 dicom.ParseFloat(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT); | |
689 } | |
690 | |
691 invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1); | |
692 } | |
693 | |
694 public: | |
695 virtual void Handle(RestApiGetCall& call, | |
696 std::auto_ptr<ImageAccessor>& decoded, | |
697 const DicomMap& dicom) ORTHANC_OVERRIDE | |
698 { | |
699 // TODO | |
700 } | |
701 | |
702 virtual bool RequiresDicomTags() const ORTHANC_OVERRIDE | |
703 { | |
704 return true; | |
705 } | |
706 }; | |
707 } | |
708 | |
537 | 709 |
538 template <enum ImageExtractionMode mode> | 710 template <enum ImageExtractionMode mode> |
539 static void GetImage(RestApiGetCall& call) | 711 static void GetImage(RestApiGetCall& call) |
540 { | 712 { |
541 ServerContext& context = OrthancRestApi::GetContext(call); | 713 GetImageHandler handler(mode); |
542 | 714 IDecodedFrameHandler::Apply(call, handler); |
543 std::string frameId = call.GetUriComponent("frame", "0"); | 715 } |
544 | 716 |
545 unsigned int frame; | 717 |
546 try | 718 static void GetRenderedFrame(RestApiGetCall& call) |
547 { | 719 { |
548 frame = boost::lexical_cast<unsigned int>(frameId); | 720 RenderedFrameHandler handler; |
549 } | 721 IDecodedFrameHandler::Apply(call, handler); |
550 catch (boost::bad_lexical_cast&) | |
551 { | |
552 return; | |
553 } | |
554 | |
555 bool invert = false; | |
556 float windowCenter = 128.0f; | |
557 float windowWidth = 256.0f; | |
558 float rescaleSlope = 1.0f; | |
559 float rescaleIntercept = 0.0f; | |
560 | |
561 std::auto_ptr<ImageAccessor> decoded; | |
562 | |
563 try | |
564 { | |
565 std::string publicId = call.GetUriComponent("id", ""); | |
566 | |
567 #if ORTHANC_ENABLE_PLUGINS == 1 | |
568 if (context.GetPlugins().HasCustomImageDecoder()) | |
569 { | |
570 // TODO create a cache of file | |
571 std::string dicomContent; | |
572 context.ReadDicom(dicomContent, publicId); | |
573 decoded.reset(context.GetPlugins().DecodeUnsafe(dicomContent.c_str(), dicomContent.size(), frame)); | |
574 | |
575 /** | |
576 * Note that we call "DecodeUnsafe()": We do not fallback to | |
577 * the builtin decoder if no installed decoder plugin is able | |
578 * to decode the image. This allows us to take advantage of | |
579 * the cache below. | |
580 **/ | |
581 | |
582 if (mode == ImageExtractionMode_Preview && | |
583 decoded.get() != NULL) | |
584 { | |
585 // TODO Optimize this lookup for photometric interpretation: | |
586 // It should be implemented by the plugin to avoid parsing | |
587 // twice the DICOM file | |
588 ParsedDicomFile parsed(dicomContent); | |
589 | |
590 LookupWindowingTags(dicomContent, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); | |
591 } | |
592 } | |
593 #endif | |
594 | |
595 if (decoded.get() == NULL) | |
596 { | |
597 // Use Orthanc's built-in decoder, using the cache to speed-up | |
598 // things on multi-frame images | |
599 ServerContext::DicomCacheLocker locker(context, publicId); | |
600 decoded.reset(DicomImageDecoder::Decode(locker.GetDicom(), frame)); | |
601 LookupWindowingTags(locker.GetDicom(), windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); | |
602 | |
603 if (mode != ImageExtractionMode_Preview) | |
604 { | |
605 invert = false; | |
606 } | |
607 } | |
608 } | |
609 catch (OrthancException& e) | |
610 { | |
611 if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange || e.GetErrorCode() == ErrorCode_UnknownResource) | |
612 { | |
613 // The frame number is out of the range for this DICOM | |
614 // instance, the resource is not existent | |
615 } | |
616 else | |
617 { | |
618 std::string root = ""; | |
619 for (size_t i = 1; i < call.GetFullUri().size(); i++) | |
620 { | |
621 root += "../"; | |
622 } | |
623 | |
624 call.GetOutput().Redirect(root + "app/images/unsupported.png"); | |
625 } | |
626 return; | |
627 } | |
628 | |
629 if (mode == ImageExtractionMode_Preview | |
630 && (decoded->GetFormat() == Orthanc::PixelFormat_Grayscale8 || decoded->GetFormat() == Orthanc::PixelFormat_Grayscale16)) | |
631 { | |
632 ImageProcessing::ApplyWindowing(*decoded, *decoded, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); | |
633 invert = false; // don't invert it later on when encoding it, it has been inverted in the ApplyWindowing function | |
634 } | |
635 | |
636 ImageToEncode image(decoded, mode, invert); | |
637 | |
638 HttpContentNegociation negociation; | |
639 EncodePng png(image); | |
640 negociation.Register(MIME_PNG, png); | |
641 | |
642 EncodeJpeg jpeg(image, call); | |
643 negociation.Register(MIME_JPEG, jpeg); | |
644 | |
645 EncodePam pam(image); | |
646 negociation.Register(MIME_PAM, pam); | |
647 | |
648 if (negociation.Apply(call.GetHttpHeaders())) | |
649 { | |
650 image.Answer(call.GetOutput()); | |
651 } | |
652 } | 722 } |
653 | 723 |
654 | 724 |
655 static void GetMatlabImage(RestApiGetCall& call) | 725 static void GetMatlabImage(RestApiGetCall& call) |
656 { | 726 { |
1800 Register("/instances/{id}/tags", GetInstanceTagsBis); | 1870 Register("/instances/{id}/tags", GetInstanceTagsBis); |
1801 Register("/instances/{id}/simplified-tags", GetInstanceTags<DicomToJsonFormat_Human>); | 1871 Register("/instances/{id}/simplified-tags", GetInstanceTags<DicomToJsonFormat_Human>); |
1802 Register("/instances/{id}/frames", ListFrames); | 1872 Register("/instances/{id}/frames", ListFrames); |
1803 | 1873 |
1804 Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>); | 1874 Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>); |
1875 Register("/instances/{id}/frames/{frame}/rendered", GetRenderedFrame); | |
1805 Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>); | 1876 Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>); |
1806 Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); | 1877 Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); |
1807 Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>); | 1878 Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>); |
1808 Register("/instances/{id}/frames/{frame}/matlab", GetMatlabImage); | 1879 Register("/instances/{id}/frames/{frame}/matlab", GetMatlabImage); |
1809 Register("/instances/{id}/frames/{frame}/raw", GetRawFrame<false>); | 1880 Register("/instances/{id}/frames/{frame}/raw", GetRawFrame<false>); |
1810 Register("/instances/{id}/frames/{frame}/raw.gz", GetRawFrame<true>); | 1881 Register("/instances/{id}/frames/{frame}/raw.gz", GetRawFrame<true>); |
1811 Register("/instances/{id}/pdf", ExtractPdf); | 1882 Register("/instances/{id}/pdf", ExtractPdf); |
1812 Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); | 1883 Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); |
1884 Register("/instances/{id}/rendered", GetRenderedFrame); | |
1813 Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); | 1885 Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); |
1814 Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); | 1886 Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); |
1815 Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>); | 1887 Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>); |
1816 Register("/instances/{id}/matlab", GetMatlabImage); | 1888 Register("/instances/{id}/matlab", GetMatlabImage); |
1817 Register("/instances/{id}/header", GetInstanceHeader); | 1889 Register("/instances/{id}/header", GetInstanceHeader); |