comparison OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.cpp @ 1512:244ad1e4e76a

reorganization of folders
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 07 Jul 2020 16:21:02 +0200
parents Framework/Loaders/SeriesThumbnailsLoader.cpp@b931ddbe070e
children e731e62692a9
comparison
equal deleted inserted replaced
1511:9dfeee74c1e6 1512:244ad1e4e76a
1 /**
2 * Stone of Orthanc
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Affero General Public License
9 * as published by the Free Software Foundation, either version 3 of
10 * the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Affero General Public License for more details.
16 *
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/>.
19 **/
20
21
22 #include "SeriesThumbnailsLoader.h"
23
24 #include "LoadedDicomResources.h"
25 #include "../Oracle/ParseDicomFromWadoCommand.h"
26 #include "../Toolbox/ImageToolbox.h"
27
28 #include <DicomFormat/DicomMap.h>
29 #include <DicomFormat/DicomInstanceHasher.h>
30 #include <Images/Image.h>
31 #include <Images/ImageProcessing.h>
32 #include <Images/JpegReader.h>
33 #include <Images/JpegWriter.h>
34 #include <OrthancException.h>
35
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
43
44 static const unsigned int JPEG_QUALITY = 70; // Only used for Orthanc source
45
46 namespace OrthancStone
47 {
48 static SeriesThumbnailType ExtractSopClassUid(const std::string& sopClassUid)
49 {
50 if (sopClassUid == "1.2.840.10008.5.1.4.1.1.104.1") // Encapsulated PDF Storage
51 {
52 return SeriesThumbnailType_Pdf;
53 }
54 else if (sopClassUid == "1.2.840.10008.5.1.4.1.1.77.1.1.1" || // Video Endoscopic Image Storage
55 sopClassUid == "1.2.840.10008.5.1.4.1.1.77.1.2.1" || // Video Microscopic Image Storage
56 sopClassUid == "1.2.840.10008.5.1.4.1.1.77.1.4.1") // Video Photographic Image Storage
57 {
58 return SeriesThumbnailType_Video;
59 }
60 else
61 {
62 return SeriesThumbnailType_Unsupported;
63 }
64 }
65
66
67 SeriesThumbnailsLoader::Thumbnail::Thumbnail(const std::string& image,
68 const std::string& mime) :
69 type_(SeriesThumbnailType_Image),
70 image_(image),
71 mime_(mime)
72 {
73 }
74
75
76 SeriesThumbnailsLoader::Thumbnail::Thumbnail(SeriesThumbnailType type) :
77 type_(type)
78 {
79 if (type == SeriesThumbnailType_Image)
80 {
81 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
82 }
83 }
84
85
86 Orthanc::ImageAccessor* SeriesThumbnailsLoader::SuccessMessage::DecodeImage() const
87 {
88 if (GetType() != SeriesThumbnailType_Image)
89 {
90 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
91 }
92
93 Orthanc::MimeType mime;
94 if (!Orthanc::LookupMimeType(mime, GetMime()))
95 {
96 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
97 "Unsupported MIME type for thumbnail: " + GetMime());
98 }
99
100 switch (mime)
101 {
102 case Orthanc::MimeType_Jpeg:
103 {
104 std::unique_ptr<Orthanc::JpegReader> reader(new Orthanc::JpegReader);
105 reader->ReadFromMemory(GetEncodedImage());
106 return reader.release();
107 }
108
109 default:
110 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
111 "Cannot decode MIME type for thumbnail: " + GetMime());
112 }
113 }
114
115
116
117 void SeriesThumbnailsLoader::AcquireThumbnail(const DicomSource& source,
118 const std::string& studyInstanceUid,
119 const std::string& seriesInstanceUid,
120 SeriesThumbnailsLoader::Thumbnail* thumbnail)
121 {
122 assert(thumbnail != NULL);
123
124 std::unique_ptr<Thumbnail> protection(thumbnail);
125
126 Thumbnails::iterator found = thumbnails_.find(seriesInstanceUid);
127 if (found == thumbnails_.end())
128 {
129 thumbnails_[seriesInstanceUid] = protection.release();
130 }
131 else
132 {
133 assert(found->second != NULL);
134 if (protection->GetType() == SeriesThumbnailType_NotLoaded ||
135 protection->GetType() == SeriesThumbnailType_Unsupported)
136 {
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
149 SuccessMessage message(*this, source, studyInstanceUid, seriesInstanceUid, *thumbnail);
150 BroadcastMessage(message);
151 }
152
153
154 class SeriesThumbnailsLoader::Handler : public Orthanc::IDynamicObject
155 {
156 private:
157 boost::shared_ptr<SeriesThumbnailsLoader> loader_;
158 DicomSource source_;
159 std::string studyInstanceUid_;
160 std::string seriesInstanceUid_;
161
162 public:
163 Handler(boost::shared_ptr<SeriesThumbnailsLoader> loader,
164 const DicomSource& source,
165 const std::string& studyInstanceUid,
166 const std::string& seriesInstanceUid) :
167 loader_(loader),
168 source_(source),
169 studyInstanceUid_(studyInstanceUid),
170 seriesInstanceUid_(seriesInstanceUid)
171 {
172 if (!loader)
173 {
174 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
175 }
176 }
177
178 boost::shared_ptr<SeriesThumbnailsLoader> GetLoader()
179 {
180 return loader_;
181 }
182
183 const DicomSource& GetSource() const
184 {
185 return source_;
186 }
187
188 const std::string& GetStudyInstanceUid() const
189 {
190 return studyInstanceUid_;
191 }
192
193 const std::string& GetSeriesInstanceUid() const
194 {
195 return seriesInstanceUid_;
196 }
197
198 virtual void HandleSuccess(const std::string& body,
199 const std::map<std::string, std::string>& headers) = 0;
200
201 virtual void HandleError()
202 {
203 LOG(INFO) << "Cannot generate thumbnail for SeriesInstanceUID: " << seriesInstanceUid_;
204 }
205 };
206
207
208 class SeriesThumbnailsLoader::DicomWebSopClassHandler : public SeriesThumbnailsLoader::Handler
209 {
210 private:
211 static bool GetSopClassUid(std::string& sopClassUid,
212 const Json::Value& json)
213 {
214 Orthanc::DicomMap dicom;
215 dicom.FromDicomWeb(json);
216
217 return dicom.LookupStringValue(sopClassUid, Orthanc::DICOM_TAG_SOP_CLASS_UID, false);
218 }
219
220 public:
221 DicomWebSopClassHandler(boost::shared_ptr<SeriesThumbnailsLoader> loader,
222 const DicomSource& source,
223 const std::string& studyInstanceUid,
224 const std::string& seriesInstanceUid) :
225 Handler(loader, source, studyInstanceUid, seriesInstanceUid)
226 {
227 }
228
229 virtual void HandleSuccess(const std::string& body,
230 const std::map<std::string, std::string>& headers)
231 {
232 Json::Reader reader;
233 Json::Value value;
234
235 if (!reader.parse(body, value) ||
236 value.type() != Json::arrayValue)
237 {
238 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
239 }
240 else
241 {
242 SeriesThumbnailType type = SeriesThumbnailType_Unsupported;
243
244 std::string sopClassUid;
245 if (value.size() > 0 &&
246 GetSopClassUid(sopClassUid, value[0]))
247 {
248 bool ok = true;
249
250 for (Json::Value::ArrayIndex i = 1; i < value.size() && ok; i++)
251 {
252 std::string s;
253 if (!GetSopClassUid(s, value[i]) ||
254 s != sopClassUid)
255 {
256 ok = false;
257 }
258 }
259
260 if (ok)
261 {
262 type = ExtractSopClassUid(sopClassUid);
263 }
264 }
265
266 GetLoader()->AcquireThumbnail(GetSource(), GetStudyInstanceUid(),
267 GetSeriesInstanceUid(), new Thumbnail(type));
268 }
269 }
270 };
271
272
273 class SeriesThumbnailsLoader::DicomWebThumbnailHandler : public SeriesThumbnailsLoader::Handler
274 {
275 public:
276 DicomWebThumbnailHandler(boost::shared_ptr<SeriesThumbnailsLoader> loader,
277 const DicomSource& source,
278 const std::string& studyInstanceUid,
279 const std::string& seriesInstanceUid) :
280 Handler(loader, source, studyInstanceUid, seriesInstanceUid)
281 {
282 }
283
284 virtual void HandleSuccess(const std::string& body,
285 const std::map<std::string, std::string>& headers)
286 {
287 std::string mime = Orthanc::MIME_JPEG;
288 for (std::map<std::string, std::string>::const_iterator
289 it = headers.begin(); it != headers.end(); ++it)
290 {
291 if (boost::iequals(it->first, "content-type"))
292 {
293 mime = it->second;
294 }
295 }
296
297 GetLoader()->AcquireThumbnail(GetSource(), GetStudyInstanceUid(),
298 GetSeriesInstanceUid(), new Thumbnail(body, mime));
299 }
300
301 virtual void HandleError()
302 {
303 // The DICOMweb wasn't able to generate a thumbnail, try to
304 // retrieve the SopClassUID tag using QIDO-RS
305
306 std::map<std::string, std::string> arguments, headers;
307 arguments["0020000D"] = GetStudyInstanceUid();
308 arguments["0020000E"] = GetSeriesInstanceUid();
309 arguments["includefield"] = "00080016"; // SOP Class UID
310
311 std::unique_ptr<IOracleCommand> command(
312 GetSource().CreateDicomWebCommand(
313 "/instances", arguments, headers, new DicomWebSopClassHandler(
314 GetLoader(), GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid())));
315 GetLoader()->Schedule(command.release());
316 }
317 };
318
319
320 class SeriesThumbnailsLoader::ThumbnailInformation : public Orthanc::IDynamicObject
321 {
322 private:
323 DicomSource source_;
324 std::string studyInstanceUid_;
325 std::string seriesInstanceUid_;
326
327 public:
328 ThumbnailInformation(const DicomSource& source,
329 const std::string& studyInstanceUid,
330 const std::string& seriesInstanceUid) :
331 source_(source),
332 studyInstanceUid_(studyInstanceUid),
333 seriesInstanceUid_(seriesInstanceUid)
334 {
335 }
336
337 const DicomSource& GetDicomSource() const
338 {
339 return source_;
340 }
341
342 const std::string& GetStudyInstanceUid() const
343 {
344 return studyInstanceUid_;
345 }
346
347 const std::string& GetSeriesInstanceUid() const
348 {
349 return seriesInstanceUid_;
350 }
351 };
352
353
354 class SeriesThumbnailsLoader::OrthancSopClassHandler : public SeriesThumbnailsLoader::Handler
355 {
356 private:
357 std::string instanceId_;
358
359 public:
360 OrthancSopClassHandler(boost::shared_ptr<SeriesThumbnailsLoader> loader,
361 const DicomSource& source,
362 const std::string& studyInstanceUid,
363 const std::string& seriesInstanceUid,
364 const std::string& instanceId) :
365 Handler(loader, source, studyInstanceUid, seriesInstanceUid),
366 instanceId_(instanceId)
367 {
368 }
369
370 virtual void HandleSuccess(const std::string& body,
371 const std::map<std::string, std::string>& headers)
372 {
373 SeriesThumbnailType type = ExtractSopClassUid(body);
374
375 if (type == SeriesThumbnailType_Pdf ||
376 type == SeriesThumbnailType_Video)
377 {
378 GetLoader()->AcquireThumbnail(GetSource(), GetStudyInstanceUid(),
379 GetSeriesInstanceUid(), new Thumbnail(type));
380 }
381 else
382 {
383 std::unique_ptr<GetOrthancImageCommand> command(new GetOrthancImageCommand);
384 command->SetUri("/instances/" + instanceId_ + "/preview");
385 command->SetHttpHeader("Accept", Orthanc::MIME_JPEG);
386 command->AcquirePayload(new ThumbnailInformation(
387 GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid()));
388 GetLoader()->Schedule(command.release());
389 }
390 }
391 };
392
393
394 class SeriesThumbnailsLoader::SelectOrthancInstanceHandler : public SeriesThumbnailsLoader::Handler
395 {
396 public:
397 SelectOrthancInstanceHandler(boost::shared_ptr<SeriesThumbnailsLoader> loader,
398 const DicomSource& source,
399 const std::string& studyInstanceUid,
400 const std::string& seriesInstanceUid) :
401 Handler(loader, source, studyInstanceUid, seriesInstanceUid)
402 {
403 }
404
405 virtual void HandleSuccess(const std::string& body,
406 const std::map<std::string, std::string>& headers)
407 {
408 static const char* const INSTANCES = "Instances";
409
410 Json::Value json;
411 Json::Reader reader;
412 if (!reader.parse(body, json) ||
413 json.type() != Json::objectValue)
414 {
415 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
416 }
417
418 if (json.isMember(INSTANCES) &&
419 json[INSTANCES].type() == Json::arrayValue &&
420 json[INSTANCES].size() > 0)
421 {
422 // Select one instance of the series to generate the thumbnail
423 Json::Value::ArrayIndex index = json[INSTANCES].size() / 2;
424 if (json[INSTANCES][index].type() == Json::stringValue)
425 {
426 std::map<std::string, std::string> arguments, headers;
427 arguments["quality"] = boost::lexical_cast<std::string>(JPEG_QUALITY);
428 headers["Accept"] = Orthanc::MIME_JPEG;
429
430 const std::string instance = json[INSTANCES][index].asString();
431
432 std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
433 command->SetUri("/instances/" + instance + "/metadata/SopClassUid");
434 command->AcquirePayload(
435 new OrthancSopClassHandler(
436 GetLoader(), GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid(), instance));
437 GetLoader()->Schedule(command.release());
438 }
439 }
440 }
441 };
442
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
490 void SeriesThumbnailsLoader::Schedule(IOracleCommand* command)
491 {
492 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
493 lock->Schedule(GetSharedObserver(), priority_, command);
494 }
495
496
497 void SeriesThumbnailsLoader::Handle(const HttpCommand::SuccessMessage& message)
498 {
499 assert(message.GetOrigin().HasPayload());
500 dynamic_cast<Handler&>(message.GetOrigin().GetPayload()).HandleSuccess(message.GetAnswer(), message.GetAnswerHeaders());
501 }
502
503
504 void SeriesThumbnailsLoader::Handle(const OrthancRestApiCommand::SuccessMessage& message)
505 {
506 assert(message.GetOrigin().HasPayload());
507 dynamic_cast<Handler&>(message.GetOrigin().GetPayload()).HandleSuccess(message.GetAnswer(), message.GetAnswerHeaders());
508 }
509
510
511 void SeriesThumbnailsLoader::Handle(const GetOrthancImageCommand::SuccessMessage& message)
512 {
513 assert(message.GetOrigin().HasPayload());
514 const ThumbnailInformation& info = dynamic_cast<ThumbnailInformation&>(message.GetOrigin().GetPayload());
515
516 std::unique_ptr<Orthanc::ImageAccessor> resized(Orthanc::ImageProcessing::FitSize(message.GetImage(), width_, height_));
517
518 std::string jpeg;
519 Orthanc::JpegWriter writer;
520 writer.SetQuality(JPEG_QUALITY);
521 writer.WriteToMemory(jpeg, *resized);
522
523 AcquireThumbnail(info.GetDicomSource(), info.GetStudyInstanceUid(),
524 info.GetSeriesInstanceUid(), new Thumbnail(jpeg, Orthanc::MIME_JPEG));
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::FitSizeKeepAspectRatio(*frame, width_, height_));
566 }
567 else
568 {
569 std::unique_ptr<Orthanc::ImageAccessor> converted(
570 new Orthanc::Image(Orthanc::PixelFormat_Float32, frame->GetWidth(), frame->GetHeight(), false));
571 Orthanc::ImageProcessing::Convert(*converted, *frame);
572
573 std::unique_ptr<Orthanc::ImageAccessor> resized(
574 Orthanc::ImageProcessing::FitSizeKeepAspectRatio(*converted, width_, height_));
575
576 float minValue, maxValue;
577 Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, *resized);
578 if (minValue + 0.01f < maxValue)
579 {
580 Orthanc::ImageProcessing::ShiftScale(*resized, -minValue, 255.0f / (maxValue - minValue), false);
581 }
582 else
583 {
584 Orthanc::ImageProcessing::Set(*resized, 0);
585 }
586
587 converted.reset(NULL);
588
589 thumbnail.reset(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width_, height_, false));
590 Orthanc::ImageProcessing::Convert(*thumbnail, *resized);
591 }
592
593 std::string jpeg;
594 Orthanc::JpegWriter writer;
595 writer.SetQuality(JPEG_QUALITY);
596 writer.WriteToMemory(jpeg, *thumbnail);
597
598 AcquireThumbnail(info.GetDicomSource(), info.GetStudyInstanceUid(),
599 info.GetSeriesInstanceUid(), new Thumbnail(jpeg, Orthanc::MIME_JPEG));
600 }
601 }
602 #endif
603
604
605 void SeriesThumbnailsLoader::Handle(const OracleCommandExceptionMessage& message)
606 {
607 const OracleCommandBase& command = dynamic_cast<const OracleCommandBase&>(message.GetOrigin());
608 assert(command.HasPayload());
609
610 if (command.GetType() == IOracleCommand::Type_GetOrthancImage)
611 {
612 // This is presumably a HTTP status 301 (Moved permanently)
613 // because of an unsupported DICOM file in "/preview"
614 const ThumbnailInformation& info = dynamic_cast<const ThumbnailInformation&>(command.GetPayload());
615 AcquireThumbnail(info.GetDicomSource(), info.GetStudyInstanceUid(),
616 info.GetSeriesInstanceUid(), new Thumbnail(SeriesThumbnailType_Unsupported));
617 }
618 else
619 {
620 dynamic_cast<Handler&>(command.GetPayload()).HandleError();
621 }
622 }
623
624
625 SeriesThumbnailsLoader::SeriesThumbnailsLoader(ILoadersContext& context,
626 int priority) :
627 context_(context),
628 priority_(priority),
629 width_(128),
630 height_(128)
631 {
632 }
633
634
635 boost::shared_ptr<SeriesThumbnailsLoader> SeriesThumbnailsLoader::Create(ILoadersContext::ILock& stone,
636 int priority)
637 {
638 boost::shared_ptr<SeriesThumbnailsLoader> result(new SeriesThumbnailsLoader(stone.GetContext(), priority));
639 result->Register<GetOrthancImageCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
640 result->Register<HttpCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
641 result->Register<OracleCommandExceptionMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
642 result->Register<OrthancRestApiCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
643
644 #if ORTHANC_ENABLE_DCMTK == 1
645 result->Register<ParseDicomSuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
646 #endif
647
648 return result;
649 }
650
651
652 void SeriesThumbnailsLoader::SetThumbnailSize(unsigned int width,
653 unsigned int height)
654 {
655 if (width <= 0 ||
656 height <= 0)
657 {
658 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
659 }
660 else
661 {
662 width_ = width;
663 height_ = height;
664 }
665 }
666
667
668 void SeriesThumbnailsLoader::Clear()
669 {
670 for (Thumbnails::iterator it = thumbnails_.begin(); it != thumbnails_.end(); ++it)
671 {
672 assert(it->second != NULL);
673 delete it->second;
674 }
675
676 thumbnails_.clear();
677 }
678
679
680 SeriesThumbnailType SeriesThumbnailsLoader::GetSeriesThumbnail(std::string& image,
681 std::string& mime,
682 const std::string& seriesInstanceUid) const
683 {
684 Thumbnails::const_iterator found = thumbnails_.find(seriesInstanceUid);
685
686 if (found == thumbnails_.end())
687 {
688 return SeriesThumbnailType_NotLoaded;
689 }
690 else
691 {
692 assert(found->second != NULL);
693 image.assign(found->second->GetImage());
694 mime.assign(found->second->GetMime());
695 return found->second->GetType();
696 }
697 }
698
699
700 void SeriesThumbnailsLoader::ScheduleLoadThumbnail(const DicomSource& source,
701 const std::string& patientId,
702 const std::string& studyInstanceUid,
703 const std::string& seriesInstanceUid)
704 {
705 if (IsScheduledSeries(seriesInstanceUid))
706 {
707 return;
708 }
709
710 if (source.IsDicomWeb())
711 {
712 if (!source.HasDicomWebRendered())
713 {
714 #if ORTHANC_ENABLE_DCMTK == 1
715 // Issue a QIDO-RS request to select one of the instances in the series
716 std::map<std::string, std::string> arguments, headers;
717 arguments["0020000D"] = studyInstanceUid;
718 arguments["0020000E"] = seriesInstanceUid;
719 arguments["includefield"] = "00080018"; // SOP Instance UID is mandatory
720
721 std::unique_ptr<IOracleCommand> command(
722 source.CreateDicomWebCommand(
723 "/instances", arguments, headers, new SelectDicomWebInstanceHandler(
724 GetSharedObserver(), source, studyInstanceUid, seriesInstanceUid)));
725 Schedule(command.release());
726 #else
727 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
728 "Stone of Orthanc was built without support to decode DICOM images");
729 #endif
730 }
731 else
732 {
733 const std::string uri = ("/studies/" + studyInstanceUid +
734 "/series/" + seriesInstanceUid + "/rendered");
735
736 std::map<std::string, std::string> arguments, headers;
737 arguments["viewport"] = (boost::lexical_cast<std::string>(width_) + "," +
738 boost::lexical_cast<std::string>(height_));
739
740 // Needed to set this header explicitly, as long as emscripten
741 // does not include macro "EMSCRIPTEN_FETCH_RESPONSE_HEADERS"
742 // https://github.com/emscripten-core/emscripten/pull/8486
743 headers["Accept"] = Orthanc::MIME_JPEG;
744
745 std::unique_ptr<IOracleCommand> command(
746 source.CreateDicomWebCommand(
747 uri, arguments, headers, new DicomWebThumbnailHandler(
748 GetSharedObserver(), source, studyInstanceUid, seriesInstanceUid)));
749 Schedule(command.release());
750 }
751
752 scheduledSeries_.insert(seriesInstanceUid);
753 }
754 else if (source.IsOrthanc())
755 {
756 // Dummy SOP Instance UID, as we are working at the "series" level
757 Orthanc::DicomInstanceHasher hasher(patientId, studyInstanceUid, seriesInstanceUid, "dummy");
758
759 std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
760 command->SetUri("/series/" + hasher.HashSeries());
761 command->AcquirePayload(new SelectOrthancInstanceHandler(
762 GetSharedObserver(), source, studyInstanceUid, seriesInstanceUid));
763 Schedule(command.release());
764
765 scheduledSeries_.insert(seriesInstanceUid);
766 }
767 else
768 {
769 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
770 "Can only load thumbnails from Orthanc or DICOMweb");
771 }
772 }
773 }