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