comparison Framework/Loaders/SeriesThumbnailsLoader.cpp @ 1484:121d01aa328e

SeriesThumbnailsLoader working on raw dicom files
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 22 Jun 2020 17:46:40 +0200
parents 4db187d29731
children b931ddbe070e
comparison
equal deleted inserted replaced
1483:6abd819aa534 1484:121d01aa328e
19 **/ 19 **/
20 20
21 21
22 #include "SeriesThumbnailsLoader.h" 22 #include "SeriesThumbnailsLoader.h"
23 23
24 #include "LoadedDicomResources.h"
25 #include "../Oracle/ParseDicomFromWadoCommand.h"
26 #include "../Toolbox/ImageToolbox.h"
27
24 #include <DicomFormat/DicomMap.h> 28 #include <DicomFormat/DicomMap.h>
25 #include <DicomFormat/DicomInstanceHasher.h> 29 #include <DicomFormat/DicomInstanceHasher.h>
30 #include <Images/Image.h>
26 #include <Images/ImageProcessing.h> 31 #include <Images/ImageProcessing.h>
27 #include <Images/JpegReader.h> 32 #include <Images/JpegReader.h>
28 #include <Images/JpegWriter.h> 33 #include <Images/JpegWriter.h>
29 #include <OrthancException.h> 34 #include <OrthancException.h>
30 35
31 #include <boost/algorithm/string/predicate.hpp> 36 #include <boost/algorithm/string/predicate.hpp>
37
38 #if ORTHANC_ENABLE_DCMTK == 1
39 # include <DicomParsing/ParsedDicomFile.h>
40 # include <DicomParsing/Internals/DicomImageDecoder.h>
41 #endif
42
32 43
33 static const unsigned int JPEG_QUALITY = 70; // Only used for Orthanc source 44 static const unsigned int JPEG_QUALITY = 70; // Only used for Orthanc source
34 45
35 namespace OrthancStone 46 namespace OrthancStone
36 { 47 {
109 SeriesThumbnailsLoader::Thumbnail* thumbnail) 120 SeriesThumbnailsLoader::Thumbnail* thumbnail)
110 { 121 {
111 assert(thumbnail != NULL); 122 assert(thumbnail != NULL);
112 123
113 std::unique_ptr<Thumbnail> protection(thumbnail); 124 std::unique_ptr<Thumbnail> protection(thumbnail);
114 125
115 Thumbnails::iterator found = thumbnails_.find(seriesInstanceUid); 126 Thumbnails::iterator found = thumbnails_.find(seriesInstanceUid);
116 if (found == thumbnails_.end()) 127 if (found == thumbnails_.end())
117 { 128 {
118 thumbnails_[seriesInstanceUid] = protection.release(); 129 thumbnails_[seriesInstanceUid] = protection.release();
119 } 130 }
120 else 131 else
121 { 132 {
122 assert(found->second != NULL); 133 assert(found->second != NULL);
123 delete found->second; 134 if (protection->GetType() == SeriesThumbnailType_NotLoaded ||
124 found->second = protection.release(); 135 protection->GetType() == SeriesThumbnailType_Unsupported)
125 } 136 {
126 137 // Don't replace an old entry if the current one is worse
138 return;
139 }
140 else
141 {
142 delete found->second;
143 found->second = protection.release();
144 }
145 }
146
147 LOG(INFO) << "Thumbnail updated for series: " << seriesInstanceUid << ": " << thumbnail->GetType();
148
127 SuccessMessage message(*this, source, studyInstanceUid, seriesInstanceUid, *thumbnail); 149 SuccessMessage message(*this, source, studyInstanceUid, seriesInstanceUid, *thumbnail);
128 BroadcastMessage(message); 150 BroadcastMessage(message);
129 } 151 }
130 152
131 153
282 // retrieve the SopClassUID tag using QIDO-RS 304 // retrieve the SopClassUID tag using QIDO-RS
283 305
284 std::map<std::string, std::string> arguments, headers; 306 std::map<std::string, std::string> arguments, headers;
285 arguments["0020000D"] = GetStudyInstanceUid(); 307 arguments["0020000D"] = GetStudyInstanceUid();
286 arguments["0020000E"] = GetSeriesInstanceUid(); 308 arguments["0020000E"] = GetSeriesInstanceUid();
287 arguments["includefield"] = "00080016"; 309 arguments["includefield"] = "00080016"; // SOP Class UID
288 310
289 std::unique_ptr<IOracleCommand> command( 311 std::unique_ptr<IOracleCommand> command(
290 GetSource().CreateDicomWebCommand( 312 GetSource().CreateDicomWebCommand(
291 "/instances", arguments, headers, new DicomWebSopClassHandler( 313 "/instances", arguments, headers, new DicomWebSopClassHandler(
292 GetLoader(), GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid()))); 314 GetLoader(), GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid())));
417 } 439 }
418 } 440 }
419 }; 441 };
420 442
421 443
444 #if ORTHANC_ENABLE_DCMTK == 1
445 class SeriesThumbnailsLoader::SelectDicomWebInstanceHandler : public SeriesThumbnailsLoader::Handler
446 {
447 public:
448 SelectDicomWebInstanceHandler(boost::shared_ptr<SeriesThumbnailsLoader> loader,
449 const DicomSource& source,
450 const std::string& studyInstanceUid,
451 const std::string& seriesInstanceUid) :
452 Handler(loader, source, studyInstanceUid, seriesInstanceUid)
453 {
454 }
455
456 virtual void HandleSuccess(const std::string& body,
457 const std::map<std::string, std::string>& headers)
458 {
459 Json::Value json;
460 Json::Reader reader;
461 if (!reader.parse(body, json) ||
462 json.type() != Json::arrayValue)
463 {
464 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
465 }
466
467 LoadedDicomResources instances(Orthanc::DICOM_TAG_SOP_INSTANCE_UID);
468 instances.AddFromDicomWeb(json);
469
470 std::string sopInstanceUid;
471 if (instances.GetSize() == 0 ||
472 !instances.GetResource(0).LookupStringValue(sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false))
473 {
474 LOG(ERROR) << "Series without an instance: " << GetSeriesInstanceUid();
475 }
476 else
477 {
478 GetLoader()->Schedule(
479 ParseDicomFromWadoCommand::Create(
480 GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid(), sopInstanceUid, false,
481 Orthanc::DicomTransferSyntax_LittleEndianExplicit /* useless, as no transcoding */,
482 new ThumbnailInformation(
483 GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid())));
484 }
485 }
486 };
487 #endif
488
489
422 void SeriesThumbnailsLoader::Schedule(IOracleCommand* command) 490 void SeriesThumbnailsLoader::Schedule(IOracleCommand* command)
423 { 491 {
424 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); 492 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
425 lock->Schedule(GetSharedObserver(), priority_, command); 493 lock->Schedule(GetSharedObserver(), priority_, command);
426 } 494 }
441 509
442 510
443 void SeriesThumbnailsLoader::Handle(const GetOrthancImageCommand::SuccessMessage& message) 511 void SeriesThumbnailsLoader::Handle(const GetOrthancImageCommand::SuccessMessage& message)
444 { 512 {
445 assert(message.GetOrigin().HasPayload()); 513 assert(message.GetOrigin().HasPayload());
514 const ThumbnailInformation& info = dynamic_cast<ThumbnailInformation&>(message.GetOrigin().GetPayload());
446 515
447 std::unique_ptr<Orthanc::ImageAccessor> resized(Orthanc::ImageProcessing::FitSize(message.GetImage(), width_, height_)); 516 std::unique_ptr<Orthanc::ImageAccessor> resized(Orthanc::ImageProcessing::FitSize(message.GetImage(), width_, height_));
448 517
449 std::string jpeg; 518 std::string jpeg;
450 Orthanc::JpegWriter writer; 519 Orthanc::JpegWriter writer;
451 writer.SetQuality(JPEG_QUALITY); 520 writer.SetQuality(JPEG_QUALITY);
452 writer.WriteToMemory(jpeg, *resized); 521 writer.WriteToMemory(jpeg, *resized);
453 522
454 const ThumbnailInformation& info = dynamic_cast<ThumbnailInformation&>(message.GetOrigin().GetPayload());
455 AcquireThumbnail(info.GetDicomSource(), info.GetStudyInstanceUid(), 523 AcquireThumbnail(info.GetDicomSource(), info.GetStudyInstanceUid(),
456 info.GetSeriesInstanceUid(), new Thumbnail(jpeg, Orthanc::MIME_JPEG)); 524 info.GetSeriesInstanceUid(), new Thumbnail(jpeg, Orthanc::MIME_JPEG));
457 } 525 }
526
527
528 #if ORTHANC_ENABLE_DCMTK == 1
529 void SeriesThumbnailsLoader::Handle(const ParseDicomSuccessMessage& message)
530 {
531 assert(message.GetOrigin().HasPayload());
532 const ParseDicomFromWadoCommand& origin =
533 dynamic_cast<const ParseDicomFromWadoCommand&>(message.GetOrigin());
534 const ThumbnailInformation& info = dynamic_cast<ThumbnailInformation&>(origin.GetPayload());
535
536 std::string tmp;
537 Orthanc::DicomTransferSyntax transferSyntax;
538 if (!message.GetDicom().LookupTransferSyntax(tmp))
539 {
540
541 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
542 "DICOM instance without a transfer syntax: " + origin.GetSopInstanceUid());
543 }
544 else if (!Orthanc::LookupTransferSyntax(transferSyntax, tmp) ||
545 !ImageToolbox::IsDecodingSupported(transferSyntax))
546 {
547 LOG(INFO) << "Asking the DICOMweb server to transcode, "
548 << "as I don't support this transfer syntax: " << tmp;
549
550 Schedule(ParseDicomFromWadoCommand::Create(
551 origin.GetSource(), info.GetStudyInstanceUid(), info.GetSeriesInstanceUid(),
552 origin.GetSopInstanceUid(), true, Orthanc::DicomTransferSyntax_LittleEndianExplicit,
553 new ThumbnailInformation(
554 origin.GetSource(), info.GetStudyInstanceUid(), info.GetSeriesInstanceUid())));
555 }
556 else
557 {
558 std::unique_ptr<Orthanc::ImageAccessor> frame(
559 Orthanc::DicomImageDecoder::Decode(message.GetDicom(), 0));
560
561 std::unique_ptr<Orthanc::ImageAccessor> thumbnail;
562
563 if (frame->GetFormat() == Orthanc::PixelFormat_RGB24)
564 {
565 thumbnail.reset(Orthanc::ImageProcessing::FitSize(*frame, width_, height_));
566 }
567 else
568 {
569 const unsigned int width = frame->GetWidth();
570 const unsigned int height = frame->GetHeight();
571
572 std::unique_ptr<Orthanc::ImageAccessor> converted(
573 new Orthanc::Image(Orthanc::PixelFormat_Float32, width, height, false));
574 Orthanc::ImageProcessing::Convert(*converted, *frame);
575
576 std::unique_ptr<Orthanc::ImageAccessor> resized(
577 Orthanc::ImageProcessing::FitSize(*converted, width, height));
578
579 float minValue, maxValue;
580 Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, *resized);
581 if (minValue + 0.01f < maxValue)
582 {
583 Orthanc::ImageProcessing::ShiftScale(*resized, -minValue, 255.0f / (maxValue - minValue), false);
584 }
585 else
586 {
587 Orthanc::ImageProcessing::Set(*resized, 0);
588 }
589
590 converted.reset(NULL);
591
592 thumbnail.reset(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false));
593 Orthanc::ImageProcessing::Convert(*thumbnail, *resized);
594 }
595
596 std::string jpeg;
597 Orthanc::JpegWriter writer;
598 writer.SetQuality(JPEG_QUALITY);
599 writer.WriteToMemory(jpeg, *thumbnail);
600
601 AcquireThumbnail(info.GetDicomSource(), info.GetStudyInstanceUid(),
602 info.GetSeriesInstanceUid(), new Thumbnail(jpeg, Orthanc::MIME_JPEG));
603 }
604 }
605 #endif
458 606
459 607
460 void SeriesThumbnailsLoader::Handle(const OracleCommandExceptionMessage& message) 608 void SeriesThumbnailsLoader::Handle(const OracleCommandExceptionMessage& message)
461 { 609 {
462 const OracleCommandBase& command = dynamic_cast<const OracleCommandBase&>(message.GetOrigin()); 610 const OracleCommandBase& command = dynamic_cast<const OracleCommandBase&>(message.GetOrigin());
493 boost::shared_ptr<SeriesThumbnailsLoader> result(new SeriesThumbnailsLoader(stone.GetContext(), priority)); 641 boost::shared_ptr<SeriesThumbnailsLoader> result(new SeriesThumbnailsLoader(stone.GetContext(), priority));
494 result->Register<GetOrthancImageCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle); 642 result->Register<GetOrthancImageCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
495 result->Register<HttpCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle); 643 result->Register<HttpCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
496 result->Register<OracleCommandExceptionMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle); 644 result->Register<OracleCommandExceptionMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
497 result->Register<OrthancRestApiCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle); 645 result->Register<OrthancRestApiCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
646
647 #if ORTHANC_ENABLE_DCMTK == 1
648 result->Register<ParseDicomSuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
649 #endif
650
498 return result; 651 return result;
499 } 652 }
500 653
501 654
502 void SeriesThumbnailsLoader::SetThumbnailSize(unsigned int width, 655 void SeriesThumbnailsLoader::SetThumbnailSize(unsigned int width,
554 { 707 {
555 if (IsScheduledSeries(seriesInstanceUid)) 708 if (IsScheduledSeries(seriesInstanceUid))
556 { 709 {
557 return; 710 return;
558 } 711 }
559 712
560 if (source.IsDicomWeb()) 713 if (source.IsDicomWeb())
561 { 714 {
562 if (!source.HasDicomWebRendered()) 715 if (!source.HasDicomWebRendered())
563 { 716 {
564 // TODO - Could use DCMTK here 717 #if ORTHANC_ENABLE_DCMTK == 1
565 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, 718 // Issue a QIDO-RS request to select one of the instances in the series
566 "DICOMweb server is not able to generate renderings of DICOM series"); 719 std::map<std::string, std::string> arguments, headers;
567 } 720 arguments["0020000D"] = studyInstanceUid;
568 721 arguments["0020000E"] = seriesInstanceUid;
569 const std::string uri = ("/studies/" + studyInstanceUid + 722 arguments["includefield"] = "00080018"; // SOP Instance UID is mandatory
570 "/series/" + seriesInstanceUid + "/rendered"); 723
571 724 std::unique_ptr<IOracleCommand> command(
572 std::map<std::string, std::string> arguments, headers; 725 source.CreateDicomWebCommand(
573 arguments["viewport"] = (boost::lexical_cast<std::string>(width_) + "," + 726 "/instances", arguments, headers, new SelectDicomWebInstanceHandler(
574 boost::lexical_cast<std::string>(height_)); 727 GetSharedObserver(), source, studyInstanceUid, seriesInstanceUid)));
575 728 Schedule(command.release());
576 // Needed to set this header explicitly, as long as emscripten 729 #else
577 // does not include macro "EMSCRIPTEN_FETCH_RESPONSE_HEADERS" 730 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
578 // https://github.com/emscripten-core/emscripten/pull/8486 731 "Stone of Orthanc was built without support to decode DICOM images");
579 headers["Accept"] = Orthanc::MIME_JPEG; 732 #endif
580 733 }
581 std::unique_ptr<IOracleCommand> command( 734 else
582 source.CreateDicomWebCommand( 735 {
583 uri, arguments, headers, new DicomWebThumbnailHandler( 736 const std::string uri = ("/studies/" + studyInstanceUid +
584 shared_from_this(), source, studyInstanceUid, seriesInstanceUid))); 737 "/series/" + seriesInstanceUid + "/rendered");
585 Schedule(command.release()); 738
739 std::map<std::string, std::string> arguments, headers;
740 arguments["viewport"] = (boost::lexical_cast<std::string>(width_) + "," +
741 boost::lexical_cast<std::string>(height_));
742
743 // Needed to set this header explicitly, as long as emscripten
744 // does not include macro "EMSCRIPTEN_FETCH_RESPONSE_HEADERS"
745 // https://github.com/emscripten-core/emscripten/pull/8486
746 headers["Accept"] = Orthanc::MIME_JPEG;
747
748 std::unique_ptr<IOracleCommand> command(
749 source.CreateDicomWebCommand(
750 uri, arguments, headers, new DicomWebThumbnailHandler(
751 GetSharedObserver(), source, studyInstanceUid, seriesInstanceUid)));
752 Schedule(command.release());
753 }
586 754
587 scheduledSeries_.insert(seriesInstanceUid); 755 scheduledSeries_.insert(seriesInstanceUid);
588 } 756 }
589 else if (source.IsOrthanc()) 757 else if (source.IsOrthanc())
590 { 758 {
592 Orthanc::DicomInstanceHasher hasher(patientId, studyInstanceUid, seriesInstanceUid, "dummy"); 760 Orthanc::DicomInstanceHasher hasher(patientId, studyInstanceUid, seriesInstanceUid, "dummy");
593 761
594 std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); 762 std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
595 command->SetUri("/series/" + hasher.HashSeries()); 763 command->SetUri("/series/" + hasher.HashSeries());
596 command->AcquirePayload(new SelectOrthancInstanceHandler( 764 command->AcquirePayload(new SelectOrthancInstanceHandler(
597 shared_from_this(), source, studyInstanceUid, seriesInstanceUid)); 765 GetSharedObserver(), source, studyInstanceUid, seriesInstanceUid));
598 Schedule(command.release()); 766 Schedule(command.release());
599 767
600 scheduledSeries_.insert(seriesInstanceUid); 768 scheduledSeries_.insert(seriesInstanceUid);
601 } 769 }
602 else 770 else