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