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);