Mercurial > hg > orthanc-stone
comparison Samples/Sdl/Loader.cpp @ 746:d716bfb3e07c
reorganization
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 22 May 2019 12:48:57 +0200 |
parents | c44c1d2d3598 |
children | ab236bb5dbc7 |
comparison
equal
deleted
inserted
replaced
745:c44c1d2d3598 | 746:d716bfb3e07c |
---|---|
17 * You should have received a copy of the GNU Affero General Public License | 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/>. | 18 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 **/ | 19 **/ |
20 | 20 |
21 | 21 |
22 #include "../../Framework/Toolbox/DicomInstanceParameters.h" | |
23 #include "../../Framework/Oracle/GetOrthancWebViewerJpegCommand.h" | |
24 #include "../../Framework/Oracle/GetOrthancImageCommand.h" | |
25 #include "../../Framework/Oracle/OrthancRestApiCommand.h" | |
22 #include "../../Framework/Oracle/SleepOracleCommand.h" | 26 #include "../../Framework/Oracle/SleepOracleCommand.h" |
23 #include "../../Framework/Oracle/OracleCommandExceptionMessage.h" | 27 #include "../../Framework/Oracle/OracleCommandExceptionMessage.h" |
24 #include "../../Framework/Messages/IMessageEmitter.h" | 28 #include "../../Framework/Messages/IMessageEmitter.h" |
25 #include "../../Framework/Oracle/OracleCommandWithPayload.h" | 29 #include "../../Framework/Oracle/OracleCommandWithPayload.h" |
26 #include "../../Framework/Oracle/IOracle.h" | 30 #include "../../Framework/Oracle/IOracle.h" |
27 | 31 |
28 // From Stone | 32 // From Stone |
29 #include "../../Framework/Loaders/BasicFetchingItemsSorter.h" | 33 #include "../../Framework/Loaders/BasicFetchingItemsSorter.h" |
30 #include "../../Framework/Loaders/BasicFetchingStrategy.h" | 34 #include "../../Framework/Loaders/BasicFetchingStrategy.h" |
31 #include "../../Framework/Messages/ICallable.h" | |
32 #include "../../Framework/Messages/IMessage.h" | |
33 #include "../../Framework/Messages/IObservable.h" | |
34 #include "../../Framework/Messages/MessageBroker.h" | |
35 #include "../../Framework/Scene2D/ColorTextureSceneLayer.h" | |
36 #include "../../Framework/Scene2D/FloatTextureSceneLayer.h" | |
37 #include "../../Framework/Scene2D/Scene2D.h" | 35 #include "../../Framework/Scene2D/Scene2D.h" |
38 #include "../../Framework/StoneInitialization.h" | 36 #include "../../Framework/StoneInitialization.h" |
39 #include "../../Framework/Toolbox/GeometryToolbox.h" | 37 #include "../../Framework/Toolbox/GeometryToolbox.h" |
40 #include "../../Framework/Toolbox/SlicesSorter.h" | 38 #include "../../Framework/Toolbox/SlicesSorter.h" |
41 #include "../../Framework/Volumes/ImageBuffer3D.h" | 39 #include "../../Framework/Volumes/ImageBuffer3D.h" |
46 #include <Core/Compression/ZlibCompressor.h> | 44 #include <Core/Compression/ZlibCompressor.h> |
47 #include <Core/DicomFormat/DicomArray.h> | 45 #include <Core/DicomFormat/DicomArray.h> |
48 #include <Core/DicomFormat/DicomImageInformation.h> | 46 #include <Core/DicomFormat/DicomImageInformation.h> |
49 #include <Core/HttpClient.h> | 47 #include <Core/HttpClient.h> |
50 #include <Core/IDynamicObject.h> | 48 #include <Core/IDynamicObject.h> |
51 #include <Core/Images/Image.h> | |
52 #include <Core/Images/ImageProcessing.h> | 49 #include <Core/Images/ImageProcessing.h> |
53 #include <Core/Images/JpegReader.h> | |
54 #include <Core/Images/PamReader.h> | |
55 #include <Core/Images/PngReader.h> | |
56 #include <Core/Images/PngWriter.h> | |
57 #include <Core/Logging.h> | 50 #include <Core/Logging.h> |
58 #include <Core/MultiThreading/SharedMessageQueue.h> | 51 #include <Core/MultiThreading/SharedMessageQueue.h> |
59 #include <Core/OrthancException.h> | 52 #include <Core/OrthancException.h> |
60 #include <Core/SystemToolbox.h> | 53 #include <Core/SystemToolbox.h> |
61 #include <Core/Toolbox.h> | 54 #include <Core/Toolbox.h> |
69 | 62 |
70 | 63 |
71 | 64 |
72 namespace OrthancStone | 65 namespace OrthancStone |
73 { | 66 { |
74 typedef std::map<std::string, std::string> HttpHeaders; | 67 class DicomVolumeImage : public boost::noncopyable |
75 | 68 { |
76 class OrthancRestApiCommand : public OracleCommandWithPayload | |
77 { | |
78 public: | |
79 class SuccessMessage : public OriginMessage<OrthancRestApiCommand> | |
80 { | |
81 ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); | |
82 | |
83 private: | |
84 HttpHeaders headers_; | |
85 std::string answer_; | |
86 | |
87 public: | |
88 SuccessMessage(const OrthancRestApiCommand& command, | |
89 const HttpHeaders& answerHeaders, | |
90 std::string& answer /* will be swapped to avoid a memcpy() */) : | |
91 OriginMessage(command), | |
92 headers_(answerHeaders), | |
93 answer_(answer) | |
94 { | |
95 } | |
96 | |
97 const std::string& GetAnswer() const | |
98 { | |
99 return answer_; | |
100 } | |
101 | |
102 void ParseJsonBody(Json::Value& target) const | |
103 { | |
104 Json::Reader reader; | |
105 if (!reader.parse(answer_, target)) | |
106 { | |
107 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
108 } | |
109 } | |
110 | |
111 const HttpHeaders& GetAnswerHeaders() const | |
112 { | |
113 return headers_; | |
114 } | |
115 }; | |
116 | |
117 | |
118 private: | 69 private: |
119 Orthanc::HttpMethod method_; | 70 std::auto_ptr<ImageBuffer3D> image_; |
120 std::string uri_; | 71 std::auto_ptr<VolumeImageGeometry> geometry_; |
121 std::string body_; | 72 std::vector<DicomInstanceParameters*> slices_; |
122 HttpHeaders headers_; | 73 uint64_t revision_; |
123 unsigned int timeout_; | 74 std::vector<uint64_t> slicesRevision_; |
124 | 75 std::vector<unsigned int> slicesQuality_; |
125 public: | |
126 OrthancRestApiCommand() : | |
127 method_(Orthanc::HttpMethod_Get), | |
128 uri_("/"), | |
129 timeout_(10) | |
130 { | |
131 } | |
132 | |
133 virtual Type GetType() const | |
134 { | |
135 return Type_OrthancRestApi; | |
136 } | |
137 | |
138 void SetMethod(Orthanc::HttpMethod method) | |
139 { | |
140 method_ = method; | |
141 } | |
142 | |
143 void SetUri(const std::string& uri) | |
144 { | |
145 uri_ = uri; | |
146 } | |
147 | |
148 void SetBody(const std::string& body) | |
149 { | |
150 body_ = body; | |
151 } | |
152 | |
153 void SetBody(const Json::Value& json) | |
154 { | |
155 Json::FastWriter writer; | |
156 body_ = writer.write(json); | |
157 } | |
158 | |
159 void SetHttpHeader(const std::string& key, | |
160 const std::string& value) | |
161 { | |
162 headers_[key] = value; | |
163 } | |
164 | |
165 Orthanc::HttpMethod GetMethod() const | |
166 { | |
167 return method_; | |
168 } | |
169 | |
170 const std::string& GetUri() const | |
171 { | |
172 return uri_; | |
173 } | |
174 | |
175 const std::string& GetBody() const | |
176 { | |
177 if (method_ == Orthanc::HttpMethod_Post || | |
178 method_ == Orthanc::HttpMethod_Put) | |
179 { | |
180 return body_; | |
181 } | |
182 else | |
183 { | |
184 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
185 } | |
186 } | |
187 | |
188 const HttpHeaders& GetHttpHeaders() const | |
189 { | |
190 return headers_; | |
191 } | |
192 | |
193 void SetTimeout(unsigned int seconds) | |
194 { | |
195 timeout_ = seconds; | |
196 } | |
197 | |
198 unsigned int GetTimeout() const | |
199 { | |
200 return timeout_; | |
201 } | |
202 }; | |
203 | |
204 | |
205 | |
206 | |
207 class GetOrthancImageCommand : public OracleCommandWithPayload | |
208 { | |
209 public: | |
210 class SuccessMessage : public OriginMessage<GetOrthancImageCommand> | |
211 { | |
212 ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); | |
213 | |
214 private: | |
215 std::auto_ptr<Orthanc::ImageAccessor> image_; | |
216 Orthanc::MimeType mime_; | |
217 | |
218 public: | |
219 SuccessMessage(const GetOrthancImageCommand& command, | |
220 Orthanc::ImageAccessor* image, // Takes ownership | |
221 Orthanc::MimeType mime) : | |
222 OriginMessage(command), | |
223 image_(image), | |
224 mime_(mime) | |
225 { | |
226 if (image == NULL) | |
227 { | |
228 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
229 } | |
230 } | |
231 | |
232 const Orthanc::ImageAccessor& GetImage() const | |
233 { | |
234 return *image_; | |
235 } | |
236 | |
237 Orthanc::MimeType GetMimeType() const | |
238 { | |
239 return mime_; | |
240 } | |
241 }; | |
242 | |
243 | |
244 private: | |
245 std::string uri_; | |
246 HttpHeaders headers_; | |
247 unsigned int timeout_; | |
248 bool hasExpectedFormat_; | |
249 Orthanc::PixelFormat expectedFormat_; | |
250 | |
251 public: | |
252 GetOrthancImageCommand() : | |
253 uri_("/"), | |
254 timeout_(10), | |
255 hasExpectedFormat_(false) | |
256 { | |
257 } | |
258 | |
259 virtual Type GetType() const | |
260 { | |
261 return Type_GetOrthancImage; | |
262 } | |
263 | |
264 void SetExpectedPixelFormat(Orthanc::PixelFormat format) | |
265 { | |
266 hasExpectedFormat_ = true; | |
267 expectedFormat_ = format; | |
268 } | |
269 | |
270 void SetUri(const std::string& uri) | |
271 { | |
272 uri_ = uri; | |
273 } | |
274 | |
275 void SetInstanceUri(const std::string& instance, | |
276 Orthanc::PixelFormat pixelFormat) | |
277 { | |
278 uri_ = "/instances/" + instance; | |
279 | |
280 switch (pixelFormat) | |
281 { | |
282 case Orthanc::PixelFormat_RGB24: | |
283 uri_ += "/preview"; | |
284 break; | |
285 | |
286 case Orthanc::PixelFormat_Grayscale16: | |
287 uri_ += "/image-uint16"; | |
288 break; | |
289 | |
290 case Orthanc::PixelFormat_SignedGrayscale16: | |
291 uri_ += "/image-int16"; | |
292 break; | |
293 | |
294 default: | |
295 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
296 } | |
297 } | |
298 | |
299 void SetHttpHeader(const std::string& key, | |
300 const std::string& value) | |
301 { | |
302 headers_[key] = value; | |
303 } | |
304 | |
305 const std::string& GetUri() const | |
306 { | |
307 return uri_; | |
308 } | |
309 | |
310 const HttpHeaders& GetHttpHeaders() const | |
311 { | |
312 return headers_; | |
313 } | |
314 | |
315 void SetTimeout(unsigned int seconds) | |
316 { | |
317 timeout_ = seconds; | |
318 } | |
319 | |
320 unsigned int GetTimeout() const | |
321 { | |
322 return timeout_; | |
323 } | |
324 | |
325 void ProcessHttpAnswer(IMessageEmitter& emitter, | |
326 const IObserver& receiver, | |
327 const std::string& answer, | |
328 const HttpHeaders& answerHeaders) const | |
329 { | |
330 Orthanc::MimeType contentType = Orthanc::MimeType_Binary; | |
331 | |
332 for (HttpHeaders::const_iterator it = answerHeaders.begin(); | |
333 it != answerHeaders.end(); ++it) | |
334 { | |
335 std::string s; | |
336 Orthanc::Toolbox::ToLowerCase(s, it->first); | |
337 | |
338 if (s == "content-type") | |
339 { | |
340 contentType = Orthanc::StringToMimeType(it->second); | |
341 break; | |
342 } | |
343 } | |
344 | |
345 std::auto_ptr<Orthanc::ImageAccessor> image; | |
346 | |
347 switch (contentType) | |
348 { | |
349 case Orthanc::MimeType_Png: | |
350 { | |
351 image.reset(new Orthanc::PngReader); | |
352 dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer); | |
353 break; | |
354 } | |
355 | |
356 case Orthanc::MimeType_Pam: | |
357 { | |
358 image.reset(new Orthanc::PamReader); | |
359 dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(answer); | |
360 break; | |
361 } | |
362 | |
363 case Orthanc::MimeType_Jpeg: | |
364 { | |
365 image.reset(new Orthanc::JpegReader); | |
366 dynamic_cast<Orthanc::JpegReader&>(*image).ReadFromMemory(answer); | |
367 break; | |
368 } | |
369 | |
370 default: | |
371 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, | |
372 "Unsupported HTTP Content-Type for an image: " + | |
373 std::string(Orthanc::EnumerationToString(contentType))); | |
374 } | |
375 | |
376 if (hasExpectedFormat_) | |
377 { | |
378 if (expectedFormat_ == Orthanc::PixelFormat_SignedGrayscale16 && | |
379 image->GetFormat() == Orthanc::PixelFormat_Grayscale16) | |
380 { | |
381 image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); | |
382 } | |
383 | |
384 if (expectedFormat_ != image->GetFormat()) | |
385 { | |
386 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); | |
387 } | |
388 } | |
389 | |
390 SuccessMessage message(*this, image.release(), contentType); | |
391 emitter.EmitMessage(receiver, message); | |
392 } | |
393 }; | |
394 | |
395 | |
396 | |
397 class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload | |
398 { | |
399 public: | |
400 class SuccessMessage : public OriginMessage<GetOrthancWebViewerJpegCommand> | |
401 { | |
402 ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); | |
403 | |
404 private: | |
405 std::auto_ptr<Orthanc::ImageAccessor> image_; | |
406 | |
407 public: | |
408 SuccessMessage(const GetOrthancWebViewerJpegCommand& command, | |
409 Orthanc::ImageAccessor* image) : // Takes ownership | |
410 OriginMessage(command), | |
411 image_(image) | |
412 { | |
413 if (image == NULL) | |
414 { | |
415 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
416 } | |
417 } | |
418 | |
419 const Orthanc::ImageAccessor& GetImage() const | |
420 { | |
421 return *image_; | |
422 } | |
423 }; | |
424 | |
425 private: | |
426 std::string instanceId_; | |
427 unsigned int frame_; | |
428 unsigned int quality_; | |
429 HttpHeaders headers_; | |
430 unsigned int timeout_; | |
431 Orthanc::PixelFormat expectedFormat_; | |
432 | |
433 public: | |
434 GetOrthancWebViewerJpegCommand() : | |
435 frame_(0), | |
436 quality_(95), | |
437 timeout_(10), | |
438 expectedFormat_(Orthanc::PixelFormat_Grayscale8) | |
439 { | |
440 } | |
441 | |
442 virtual Type GetType() const | |
443 { | |
444 return Type_GetOrthancWebViewerJpeg; | |
445 } | |
446 | |
447 void SetExpectedPixelFormat(Orthanc::PixelFormat format) | |
448 { | |
449 expectedFormat_ = format; | |
450 } | |
451 | |
452 void SetInstance(const std::string& instanceId) | |
453 { | |
454 instanceId_ = instanceId; | |
455 } | |
456 | |
457 void SetFrame(unsigned int frame) | |
458 { | |
459 frame_ = frame; | |
460 } | |
461 | |
462 void SetQuality(unsigned int quality) | |
463 { | |
464 if (quality <= 0 || | |
465 quality > 100) | |
466 { | |
467 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
468 } | |
469 else | |
470 { | |
471 quality_ = quality; | |
472 } | |
473 } | |
474 | |
475 void SetHttpHeader(const std::string& key, | |
476 const std::string& value) | |
477 { | |
478 headers_[key] = value; | |
479 } | |
480 | |
481 Orthanc::PixelFormat GetExpectedPixelFormat() const | |
482 { | |
483 return expectedFormat_; | |
484 } | |
485 | |
486 const std::string& GetInstanceId() const | |
487 { | |
488 return instanceId_; | |
489 } | |
490 | |
491 unsigned int GetFrame() const | |
492 { | |
493 return frame_; | |
494 } | |
495 | |
496 unsigned int GetQuality() const | |
497 { | |
498 return quality_; | |
499 } | |
500 | |
501 const HttpHeaders& GetHttpHeaders() const | |
502 { | |
503 return headers_; | |
504 } | |
505 | |
506 void SetTimeout(unsigned int seconds) | |
507 { | |
508 timeout_ = seconds; | |
509 } | |
510 | |
511 unsigned int GetTimeout() const | |
512 { | |
513 return timeout_; | |
514 } | |
515 | |
516 std::string GetUri() const | |
517 { | |
518 return ("/web-viewer/instances/jpeg" + boost::lexical_cast<std::string>(quality_) + | |
519 "-" + instanceId_ + "_" + boost::lexical_cast<std::string>(frame_)); | |
520 } | |
521 | |
522 void ProcessHttpAnswer(IMessageEmitter& emitter, | |
523 const IObserver& receiver, | |
524 const std::string& answer) const | |
525 { | |
526 // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()" | |
527 | |
528 Json::Value encoded; | |
529 | |
530 { | |
531 Json::Reader reader; | |
532 if (!reader.parse(answer, encoded)) | |
533 { | |
534 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
535 } | |
536 } | |
537 | |
538 if (encoded.type() != Json::objectValue || | |
539 !encoded.isMember("Orthanc") || | |
540 encoded["Orthanc"].type() != Json::objectValue) | |
541 { | |
542 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
543 } | |
544 | |
545 const Json::Value& info = encoded["Orthanc"]; | |
546 if (!info.isMember("PixelData") || | |
547 !info.isMember("Stretched") || | |
548 !info.isMember("Compression") || | |
549 info["Compression"].type() != Json::stringValue || | |
550 info["PixelData"].type() != Json::stringValue || | |
551 info["Stretched"].type() != Json::booleanValue || | |
552 info["Compression"].asString() != "Jpeg") | |
553 { | |
554 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
555 } | |
556 | |
557 bool isSigned = false; | |
558 bool isStretched = info["Stretched"].asBool(); | |
559 | |
560 if (info.isMember("IsSigned")) | |
561 { | |
562 if (info["IsSigned"].type() != Json::booleanValue) | |
563 { | |
564 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
565 } | |
566 else | |
567 { | |
568 isSigned = info["IsSigned"].asBool(); | |
569 } | |
570 } | |
571 | |
572 std::auto_ptr<Orthanc::ImageAccessor> reader; | |
573 | |
574 { | |
575 std::string jpeg; | |
576 Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString()); | |
577 | |
578 reader.reset(new Orthanc::JpegReader); | |
579 dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg); | |
580 } | |
581 | |
582 if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image | |
583 { | |
584 if (expectedFormat_ != Orthanc::PixelFormat_RGB24) | |
585 { | |
586 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
587 } | |
588 | |
589 if (isSigned || isStretched) | |
590 { | |
591 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
592 } | |
593 else | |
594 { | |
595 SuccessMessage message(*this, reader.release()); | |
596 emitter.EmitMessage(receiver, message); | |
597 return; | |
598 } | |
599 } | |
600 | |
601 if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8) | |
602 { | |
603 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
604 } | |
605 | |
606 if (!isStretched) | |
607 { | |
608 if (expectedFormat_ != reader->GetFormat()) | |
609 { | |
610 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
611 } | |
612 else | |
613 { | |
614 SuccessMessage message(*this, reader.release()); | |
615 emitter.EmitMessage(receiver, message); | |
616 return; | |
617 } | |
618 } | |
619 | |
620 int32_t stretchLow = 0; | |
621 int32_t stretchHigh = 0; | |
622 | |
623 if (!info.isMember("StretchLow") || | |
624 !info.isMember("StretchHigh") || | |
625 info["StretchLow"].type() != Json::intValue || | |
626 info["StretchHigh"].type() != Json::intValue) | |
627 { | |
628 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
629 } | |
630 | |
631 stretchLow = info["StretchLow"].asInt(); | |
632 stretchHigh = info["StretchHigh"].asInt(); | |
633 | |
634 if (stretchLow < -32768 || | |
635 stretchHigh > 65535 || | |
636 (stretchLow < 0 && stretchHigh > 32767)) | |
637 { | |
638 // This range cannot be represented with a uint16_t or an int16_t | |
639 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
640 } | |
641 | |
642 // Decode a grayscale JPEG 8bpp image coming from the Web viewer | |
643 std::auto_ptr<Orthanc::ImageAccessor> image | |
644 (new Orthanc::Image(expectedFormat_, reader->GetWidth(), reader->GetHeight(), false)); | |
645 | |
646 Orthanc::ImageProcessing::Convert(*image, *reader); | |
647 reader.reset(); | |
648 | |
649 float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f; | |
650 | |
651 if (!LinearAlgebra::IsCloseToZero(scaling)) | |
652 { | |
653 float offset = static_cast<float>(stretchLow) / scaling; | |
654 Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); | |
655 } | |
656 | |
657 SuccessMessage message(*this, image.release()); | |
658 emitter.EmitMessage(receiver, message); | |
659 } | |
660 }; | |
661 | |
662 | |
663 | |
664 | |
665 | |
666 class DicomInstanceParameters : | |
667 public Orthanc::IDynamicObject /* to be used as a payload of SlicesSorter */ | |
668 { | |
669 private: | |
670 struct Data // Struct to ease the copy constructor | |
671 { | |
672 std::string orthancInstanceId_; | |
673 std::string studyInstanceUid_; | |
674 std::string seriesInstanceUid_; | |
675 std::string sopInstanceUid_; | |
676 Orthanc::DicomImageInformation imageInformation_; | |
677 SopClassUid sopClassUid_; | |
678 double thickness_; | |
679 double pixelSpacingX_; | |
680 double pixelSpacingY_; | |
681 CoordinateSystem3D geometry_; | |
682 Vector frameOffsets_; | |
683 bool isColor_; | |
684 bool hasRescale_; | |
685 double rescaleIntercept_; | |
686 double rescaleSlope_; | |
687 bool hasDefaultWindowing_; | |
688 float defaultWindowingCenter_; | |
689 float defaultWindowingWidth_; | |
690 Orthanc::PixelFormat expectedPixelFormat_; | |
691 | |
692 void ComputeDoseOffsets(const Orthanc::DicomMap& dicom) | |
693 { | |
694 // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html | |
695 | |
696 { | |
697 std::string increment; | |
698 | |
699 if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false)) | |
700 { | |
701 Orthanc::Toolbox::ToUpperCase(increment); | |
702 if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag | |
703 { | |
704 LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag"; | |
705 return; | |
706 } | |
707 } | |
708 } | |
709 | |
710 if (!LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) || | |
711 frameOffsets_.size() < imageInformation_.GetNumberOfFrames()) | |
712 { | |
713 LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)"; | |
714 frameOffsets_.clear(); | |
715 } | |
716 else | |
717 { | |
718 if (frameOffsets_.size() >= 2) | |
719 { | |
720 thickness_ = frameOffsets_[1] - frameOffsets_[0]; | |
721 | |
722 if (thickness_ < 0) | |
723 { | |
724 thickness_ = -thickness_; | |
725 } | |
726 } | |
727 } | |
728 } | |
729 | |
730 Data(const Orthanc::DicomMap& dicom) : | |
731 imageInformation_(dicom) | |
732 { | |
733 if (imageInformation_.GetNumberOfFrames() <= 0) | |
734 { | |
735 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
736 } | |
737 | |
738 if (!dicom.CopyToString(studyInstanceUid_, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) || | |
739 !dicom.CopyToString(seriesInstanceUid_, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) || | |
740 !dicom.CopyToString(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) | |
741 { | |
742 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
743 } | |
744 | |
745 std::string s; | |
746 if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false)) | |
747 { | |
748 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
749 } | |
750 else | |
751 { | |
752 sopClassUid_ = StringToSopClassUid(s); | |
753 } | |
754 | |
755 if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS)) | |
756 { | |
757 thickness_ = 100.0 * std::numeric_limits<double>::epsilon(); | |
758 } | |
759 | |
760 GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom); | |
761 | |
762 std::string position, orientation; | |
763 if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) && | |
764 dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)) | |
765 { | |
766 geometry_ = CoordinateSystem3D(position, orientation); | |
767 } | |
768 | |
769 if (sopClassUid_ == SopClassUid_RTDose) | |
770 { | |
771 ComputeDoseOffsets(dicom); | |
772 } | |
773 | |
774 isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 && | |
775 imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2); | |
776 | |
777 double doseGridScaling; | |
778 | |
779 if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && | |
780 dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE)) | |
781 { | |
782 hasRescale_ = true; | |
783 } | |
784 else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) | |
785 { | |
786 hasRescale_ = true; | |
787 rescaleIntercept_ = 0; | |
788 rescaleSlope_ = doseGridScaling; | |
789 } | |
790 else | |
791 { | |
792 hasRescale_ = false; | |
793 } | |
794 | |
795 Vector c, w; | |
796 if (LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) && | |
797 LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) && | |
798 c.size() > 0 && | |
799 w.size() > 0) | |
800 { | |
801 hasDefaultWindowing_ = true; | |
802 defaultWindowingCenter_ = static_cast<float>(c[0]); | |
803 defaultWindowingWidth_ = static_cast<float>(w[0]); | |
804 } | |
805 else | |
806 { | |
807 hasDefaultWindowing_ = false; | |
808 } | |
809 | |
810 if (sopClassUid_ == SopClassUid_RTDose) | |
811 { | |
812 switch (imageInformation_.GetBitsStored()) | |
813 { | |
814 case 16: | |
815 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; | |
816 break; | |
817 | |
818 case 32: | |
819 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32; | |
820 break; | |
821 | |
822 default: | |
823 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
824 } | |
825 } | |
826 else if (isColor_) | |
827 { | |
828 expectedPixelFormat_ = Orthanc::PixelFormat_RGB24; | |
829 } | |
830 else if (imageInformation_.IsSigned()) | |
831 { | |
832 expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16; | |
833 } | |
834 else | |
835 { | |
836 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; | |
837 } | |
838 } | |
839 | |
840 CoordinateSystem3D GetFrameGeometry(unsigned int frame) const | |
841 { | |
842 if (frame == 0) | |
843 { | |
844 return geometry_; | |
845 } | |
846 else if (frame >= imageInformation_.GetNumberOfFrames()) | |
847 { | |
848 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
849 } | |
850 else if (sopClassUid_ == SopClassUid_RTDose) | |
851 { | |
852 if (frame >= frameOffsets_.size()) | |
853 { | |
854 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
855 } | |
856 | |
857 return CoordinateSystem3D( | |
858 geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(), | |
859 geometry_.GetAxisX(), | |
860 geometry_.GetAxisY()); | |
861 } | |
862 else | |
863 { | |
864 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
865 } | |
866 } | |
867 | |
868 // TODO - Is this necessary? | |
869 bool FrameContainsPlane(unsigned int frame, | |
870 const CoordinateSystem3D& plane) const | |
871 { | |
872 if (frame >= imageInformation_.GetNumberOfFrames()) | |
873 { | |
874 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
875 } | |
876 | |
877 CoordinateSystem3D tmp = geometry_; | |
878 | |
879 if (frame != 0) | |
880 { | |
881 tmp = GetFrameGeometry(frame); | |
882 } | |
883 | |
884 double distance; | |
885 | |
886 return (CoordinateSystem3D::GetDistance(distance, tmp, plane) && | |
887 distance <= thickness_ / 2.0); | |
888 } | |
889 | |
890 | |
891 void ApplyRescale(Orthanc::ImageAccessor& image, | |
892 bool useDouble) const | |
893 { | |
894 if (image.GetFormat() != Orthanc::PixelFormat_Float32) | |
895 { | |
896 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); | |
897 } | |
898 | |
899 if (hasRescale_) | |
900 { | |
901 const unsigned int width = image.GetWidth(); | |
902 const unsigned int height = image.GetHeight(); | |
903 | |
904 for (unsigned int y = 0; y < height; y++) | |
905 { | |
906 float* p = reinterpret_cast<float*>(image.GetRow(y)); | |
907 | |
908 if (useDouble) | |
909 { | |
910 // Slower, accurate implementation using double | |
911 for (unsigned int x = 0; x < width; x++, p++) | |
912 { | |
913 double value = static_cast<double>(*p); | |
914 *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_); | |
915 } | |
916 } | |
917 else | |
918 { | |
919 // Fast, approximate implementation using float | |
920 for (unsigned int x = 0; x < width; x++, p++) | |
921 { | |
922 *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_); | |
923 } | |
924 } | |
925 } | |
926 } | |
927 } | |
928 }; | |
929 | |
930 | |
931 Data data_; | |
932 | |
933 | |
934 public: | |
935 DicomInstanceParameters(const DicomInstanceParameters& other) : | |
936 data_(other.data_) | |
937 { | |
938 } | |
939 | |
940 DicomInstanceParameters(const Orthanc::DicomMap& dicom) : | |
941 data_(dicom) | |
942 { | |
943 } | |
944 | |
945 void SetOrthancInstanceIdentifier(const std::string& id) | |
946 { | |
947 data_.orthancInstanceId_ = id; | |
948 } | |
949 | |
950 const std::string& GetOrthancInstanceIdentifier() const | |
951 { | |
952 return data_.orthancInstanceId_; | |
953 } | |
954 | |
955 const Orthanc::DicomImageInformation& GetImageInformation() const | |
956 { | |
957 return data_.imageInformation_; | |
958 } | |
959 | |
960 const std::string& GetStudyInstanceUid() const | |
961 { | |
962 return data_.studyInstanceUid_; | |
963 } | |
964 | |
965 const std::string& GetSeriesInstanceUid() const | |
966 { | |
967 return data_.seriesInstanceUid_; | |
968 } | |
969 | |
970 const std::string& GetSopInstanceUid() const | |
971 { | |
972 return data_.sopInstanceUid_; | |
973 } | |
974 | |
975 SopClassUid GetSopClassUid() const | |
976 { | |
977 return data_.sopClassUid_; | |
978 } | |
979 | |
980 double GetThickness() const | |
981 { | |
982 return data_.thickness_; | |
983 } | |
984 | |
985 double GetPixelSpacingX() const | |
986 { | |
987 return data_.pixelSpacingX_; | |
988 } | |
989 | |
990 double GetPixelSpacingY() const | |
991 { | |
992 return data_.pixelSpacingY_; | |
993 } | |
994 | |
995 const CoordinateSystem3D& GetGeometry() const | |
996 { | |
997 return data_.geometry_; | |
998 } | |
999 | |
1000 CoordinateSystem3D GetFrameGeometry(unsigned int frame) const | |
1001 { | |
1002 return data_.GetFrameGeometry(frame); | |
1003 } | |
1004 | |
1005 // TODO - Is this necessary? | |
1006 bool FrameContainsPlane(unsigned int frame, | |
1007 const CoordinateSystem3D& plane) const | |
1008 { | |
1009 return data_.FrameContainsPlane(frame, plane); | |
1010 } | |
1011 | |
1012 bool IsColor() const | |
1013 { | |
1014 return data_.isColor_; | |
1015 } | |
1016 | |
1017 bool HasRescale() const | |
1018 { | |
1019 return data_.hasRescale_; | |
1020 } | |
1021 | |
1022 double GetRescaleIntercept() const | |
1023 { | |
1024 if (data_.hasRescale_) | |
1025 { | |
1026 return data_.rescaleIntercept_; | |
1027 } | |
1028 else | |
1029 { | |
1030 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1031 } | |
1032 } | |
1033 | |
1034 double GetRescaleSlope() const | |
1035 { | |
1036 if (data_.hasRescale_) | |
1037 { | |
1038 return data_.rescaleSlope_; | |
1039 } | |
1040 else | |
1041 { | |
1042 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1043 } | |
1044 } | |
1045 | |
1046 bool HasDefaultWindowing() const | |
1047 { | |
1048 return data_.hasDefaultWindowing_; | |
1049 } | |
1050 | |
1051 float GetDefaultWindowingCenter() const | |
1052 { | |
1053 if (data_.hasDefaultWindowing_) | |
1054 { | |
1055 return data_.defaultWindowingCenter_; | |
1056 } | |
1057 else | |
1058 { | |
1059 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1060 } | |
1061 } | |
1062 | |
1063 float GetDefaultWindowingWidth() const | |
1064 { | |
1065 if (data_.hasDefaultWindowing_) | |
1066 { | |
1067 return data_.defaultWindowingWidth_; | |
1068 } | |
1069 else | |
1070 { | |
1071 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1072 } | |
1073 } | |
1074 | |
1075 Orthanc::PixelFormat GetExpectedPixelFormat() const | |
1076 { | |
1077 return data_.expectedPixelFormat_; | |
1078 } | |
1079 | |
1080 | |
1081 TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& source) const | |
1082 { | |
1083 assert(sizeof(float) == 4); | |
1084 | |
1085 Orthanc::PixelFormat sourceFormat = source.GetFormat(); | |
1086 | |
1087 if (sourceFormat != GetExpectedPixelFormat()) | |
1088 { | |
1089 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); | |
1090 } | |
1091 | |
1092 if (sourceFormat == Orthanc::PixelFormat_RGB24) | |
1093 { | |
1094 // This is the case of a color image. No conversion has to be done. | |
1095 return new ColorTextureSceneLayer(source); | |
1096 } | |
1097 else | |
1098 { | |
1099 if (sourceFormat != Orthanc::PixelFormat_Grayscale16 && | |
1100 sourceFormat != Orthanc::PixelFormat_Grayscale32 && | |
1101 sourceFormat != Orthanc::PixelFormat_SignedGrayscale16) | |
1102 { | |
1103 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
1104 } | |
1105 | |
1106 std::auto_ptr<FloatTextureSceneLayer> texture; | |
1107 | |
1108 { | |
1109 // This is the case of a grayscale frame. Convert it to Float32. | |
1110 std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, | |
1111 source.GetWidth(), | |
1112 source.GetHeight(), | |
1113 false)); | |
1114 Orthanc::ImageProcessing::Convert(*converted, source); | |
1115 | |
1116 // Correct rescale slope/intercept if need be | |
1117 data_.ApplyRescale(*converted, (sourceFormat == Orthanc::PixelFormat_Grayscale32)); | |
1118 | |
1119 texture.reset(new FloatTextureSceneLayer(*converted)); | |
1120 } | |
1121 | |
1122 if (data_.hasDefaultWindowing_) | |
1123 { | |
1124 texture->SetCustomWindowing(data_.defaultWindowingCenter_, | |
1125 data_.defaultWindowingWidth_); | |
1126 } | |
1127 | |
1128 return texture.release(); | |
1129 } | |
1130 } | |
1131 }; | |
1132 | |
1133 | |
1134 class DicomVolumeImage : public boost::noncopyable | |
1135 { | |
1136 private: | |
1137 std::auto_ptr<ImageBuffer3D> image_; | |
1138 std::auto_ptr<VolumeImageGeometry> geometry_; | |
1139 std::vector<DicomInstanceParameters*> slices_; | |
1140 uint64_t revision_; | |
1141 std::vector<uint64_t> slicesRevision_; | |
1142 std::vector<unsigned int> slicesQuality_; | |
1143 | 76 |
1144 void CheckSlice(size_t index, | 77 void CheckSlice(size_t index, |
1145 const DicomInstanceParameters& reference) const | 78 const DicomInstanceParameters& reference) const |
1146 { | 79 { |
1147 const DicomInstanceParameters& slice = *slices_[index]; | 80 const DicomInstanceParameters& slice = *slices_[index]; |
1378 } | 311 } |
1379 }; | 312 }; |
1380 | 313 |
1381 | 314 |
1382 | 315 |
1383 class IDicomVolumeSource : public boost::noncopyable | 316 class IDicomVolumeImageSource : public boost::noncopyable |
1384 { | 317 { |
1385 public: | 318 public: |
1386 virtual ~IDicomVolumeSource() | 319 virtual ~IDicomVolumeImageSource() |
1387 { | 320 { |
1388 } | 321 } |
1389 | 322 |
1390 virtual const DicomVolumeImage& GetVolume() const = 0; | 323 virtual const DicomVolumeImage& GetVolume() const = 0; |
1391 | 324 |
1394 | 327 |
1395 | 328 |
1396 | 329 |
1397 class VolumeSeriesOrthancLoader : | 330 class VolumeSeriesOrthancLoader : |
1398 public IObserver, | 331 public IObserver, |
1399 public IDicomVolumeSource | 332 public IDicomVolumeImageSource |
1400 { | 333 { |
1401 private: | 334 private: |
1402 static const unsigned int LOW_QUALITY = 0; | 335 static const unsigned int LOW_QUALITY = 0; |
1403 static const unsigned int MIDDLE_QUALITY = 1; | 336 static const unsigned int MIDDLE_QUALITY = 1; |
1404 static const unsigned int BEST_QUALITY = 2; | 337 static const unsigned int BEST_QUALITY = 2; |
1685 | 618 |
1686 | 619 |
1687 class DicomVolumeMPRSlicer : public IVolumeSlicer | 620 class DicomVolumeMPRSlicer : public IVolumeSlicer |
1688 { | 621 { |
1689 private: | 622 private: |
1690 bool linearInterpolation_; | 623 bool linearInterpolation_; |
1691 Scene2D& scene_; | 624 Scene2D& scene_; |
1692 int layerDepth_; | 625 int layerDepth_; |
1693 IDicomVolumeSource& source_; | 626 IDicomVolumeImageSource& source_; |
1694 bool first_; | 627 bool first_; |
1695 VolumeProjection lastProjection_; | 628 VolumeProjection lastProjection_; |
1696 unsigned int lastSliceIndex_; | 629 unsigned int lastSliceIndex_; |
1697 uint64_t lastSliceRevision_; | 630 uint64_t lastSliceRevision_; |
1698 | 631 |
1699 public: | 632 public: |
1700 DicomVolumeMPRSlicer(Scene2D& scene, | 633 DicomVolumeMPRSlicer(Scene2D& scene, |
1701 int layerDepth, | 634 int layerDepth, |
1702 IDicomVolumeSource& source) : | 635 IDicomVolumeImageSource& source) : |
1703 linearInterpolation_(false), | 636 linearInterpolation_(false), |
1704 scene_(scene), | 637 scene_(scene), |
1705 layerDepth_(layerDepth), | 638 layerDepth_(layerDepth), |
1706 source_(source), | 639 source_(source), |
1707 first_(true) | 640 first_(true) |
1812 | 745 |
1813 | 746 |
1814 class ThreadedOracle : public IOracle | 747 class ThreadedOracle : public IOracle |
1815 { | 748 { |
1816 private: | 749 private: |
750 typedef std::map<std::string, std::string> HttpHeaders; | |
751 | |
1817 class Item : public Orthanc::IDynamicObject | 752 class Item : public Orthanc::IDynamicObject |
1818 { | 753 { |
1819 private: | 754 private: |
1820 const IObserver& receiver_; | 755 const IObserver& receiver_; |
1821 std::auto_ptr<IOracleCommand> command_; | 756 std::auto_ptr<IOracleCommand> command_; |