Mercurial > hg > orthanc-stone
comparison Samples/Sdl/Loader.cpp @ 633:b0652595b62a
Merge
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Thu, 09 May 2019 14:38:27 +0200 |
parents | ea8322566596 |
children | fb00a8be03e2 |
comparison
equal
deleted
inserted
replaced
632:500c3f70b6c2 | 633:b0652595b62a |
---|---|
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-2019 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 // From Stone | |
22 #include "../../Framework/Messages/ICallable.h" | |
23 #include "../../Framework/Messages/IMessage.h" | |
24 #include "../../Framework/Messages/IObservable.h" | |
25 #include "../../Framework/Messages/MessageBroker.h" | |
26 #include "../../Framework/StoneInitialization.h" | |
27 #include "../../Framework/Toolbox/GeometryToolbox.h" | |
28 #include "../../Framework/Volumes/ImageBuffer3D.h" | |
29 | |
30 // From Orthanc framework | |
31 #include <Core/DicomFormat/DicomArray.h> | |
32 #include <Core/DicomFormat/DicomImageInformation.h> | |
33 #include <Core/HttpClient.h> | |
34 #include <Core/IDynamicObject.h> | |
35 #include <Core/Images/Image.h> | |
36 #include <Core/Images/ImageProcessing.h> | |
37 #include <Core/Images/PngWriter.h> | |
38 #include <Core/Logging.h> | |
39 #include <Core/MultiThreading/SharedMessageQueue.h> | |
40 #include <Core/OrthancException.h> | |
41 #include <Core/Toolbox.h> | |
42 | |
43 #include <json/reader.h> | |
44 #include <json/value.h> | |
45 #include <json/writer.h> | |
46 | |
47 #include <list> | |
48 #include <stdio.h> | |
49 | |
50 | |
51 | |
52 namespace Refactoring | |
53 { | |
54 class IOracleCommand : public boost::noncopyable | |
55 { | |
56 public: | |
57 enum Type | |
58 { | |
59 Type_OrthancApi | |
60 }; | |
61 | |
62 virtual ~IOracleCommand() | |
63 { | |
64 } | |
65 | |
66 virtual Type GetType() const = 0; | |
67 }; | |
68 | |
69 | |
70 class IMessageEmitter : public boost::noncopyable | |
71 { | |
72 public: | |
73 virtual ~IMessageEmitter() | |
74 { | |
75 } | |
76 | |
77 virtual void EmitMessage(const OrthancStone::IObserver& observer, | |
78 const OrthancStone::IMessage& message) = 0; | |
79 }; | |
80 | |
81 | |
82 class IOracle : public boost::noncopyable | |
83 { | |
84 public: | |
85 virtual ~IOracle() | |
86 { | |
87 } | |
88 | |
89 virtual void Schedule(const OrthancStone::IObserver& receiver, | |
90 IOracleCommand* command) = 0; // Takes ownership | |
91 }; | |
92 | |
93 | |
94 | |
95 | |
96 class OracleCommandWithPayload : public IOracleCommand | |
97 { | |
98 private: | |
99 std::auto_ptr<Orthanc::IDynamicObject> payload_; | |
100 | |
101 public: | |
102 void SetPayload(Orthanc::IDynamicObject* payload) | |
103 { | |
104 if (payload == NULL) | |
105 { | |
106 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
107 } | |
108 else | |
109 { | |
110 payload_.reset(payload); | |
111 } | |
112 } | |
113 | |
114 bool HasPayload() const | |
115 { | |
116 return (payload_.get() != NULL); | |
117 } | |
118 | |
119 const Orthanc::IDynamicObject& GetPayload() const | |
120 { | |
121 if (HasPayload()) | |
122 { | |
123 return *payload_; | |
124 } | |
125 else | |
126 { | |
127 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
128 } | |
129 } | |
130 }; | |
131 | |
132 | |
133 | |
134 typedef std::map<std::string, std::string> HttpHeaders; | |
135 | |
136 class OrthancApiOracleCommand : public OracleCommandWithPayload | |
137 { | |
138 public: | |
139 class SuccessMessage : public OrthancStone::OriginMessage<OrthancStone::MessageType_HttpRequestSuccess, // TODO | |
140 OrthancApiOracleCommand> | |
141 { | |
142 private: | |
143 HttpHeaders headers_; | |
144 std::string answer_; | |
145 | |
146 public: | |
147 SuccessMessage(const OrthancApiOracleCommand& command, | |
148 const HttpHeaders& answerHeaders, | |
149 std::string& answer /* will be swapped to avoid a memcpy() */) : | |
150 OriginMessage(command), | |
151 headers_(answerHeaders), | |
152 answer_(answer) | |
153 { | |
154 } | |
155 | |
156 const std::string& GetAnswer() const | |
157 { | |
158 return answer_; | |
159 } | |
160 | |
161 void ParseJsonBody(Json::Value& target) const | |
162 { | |
163 Json::Reader reader; | |
164 if (!reader.parse(answer_, target)) | |
165 { | |
166 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
167 } | |
168 } | |
169 | |
170 const HttpHeaders& GetAnswerHeaders() const | |
171 { | |
172 return headers_; | |
173 } | |
174 }; | |
175 | |
176 | |
177 class FailureMessage : public OrthancStone::OriginMessage<OrthancStone::MessageType_HttpRequestError, // TODO | |
178 OrthancApiOracleCommand> | |
179 { | |
180 private: | |
181 Orthanc::HttpStatus status_; | |
182 | |
183 public: | |
184 FailureMessage(const OrthancApiOracleCommand& command, | |
185 Orthanc::HttpStatus status) : | |
186 OriginMessage(command), | |
187 status_(status) | |
188 { | |
189 } | |
190 | |
191 Orthanc::HttpStatus GetHttpStatus() const | |
192 { | |
193 return status_; | |
194 } | |
195 }; | |
196 | |
197 | |
198 private: | |
199 Orthanc::HttpMethod method_; | |
200 std::string uri_; | |
201 std::string body_; | |
202 HttpHeaders headers_; | |
203 unsigned int timeout_; | |
204 | |
205 std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> > successCallback_; | |
206 std::auto_ptr< OrthancStone::MessageHandler<FailureMessage> > failureCallback_; | |
207 | |
208 public: | |
209 OrthancApiOracleCommand() : | |
210 method_(Orthanc::HttpMethod_Get), | |
211 uri_("/"), | |
212 timeout_(10) | |
213 { | |
214 } | |
215 | |
216 virtual Type GetType() const | |
217 { | |
218 return Type_OrthancApi; | |
219 } | |
220 | |
221 void SetMethod(Orthanc::HttpMethod method) | |
222 { | |
223 method_ = method; | |
224 } | |
225 | |
226 void SetUri(const std::string& uri) | |
227 { | |
228 uri_ = uri; | |
229 } | |
230 | |
231 void SetBody(const std::string& body) | |
232 { | |
233 body_ = body; | |
234 } | |
235 | |
236 void SetBody(const Json::Value& json) | |
237 { | |
238 Json::FastWriter writer; | |
239 body_ = writer.write(json); | |
240 } | |
241 | |
242 void SetHttpHeader(const std::string& key, | |
243 const std::string& value) | |
244 { | |
245 headers_[key] = value; | |
246 } | |
247 | |
248 Orthanc::HttpMethod GetMethod() const | |
249 { | |
250 return method_; | |
251 } | |
252 | |
253 const std::string& GetUri() const | |
254 { | |
255 return uri_; | |
256 } | |
257 | |
258 const std::string& GetBody() const | |
259 { | |
260 if (method_ == Orthanc::HttpMethod_Post || | |
261 method_ == Orthanc::HttpMethod_Put) | |
262 { | |
263 return body_; | |
264 } | |
265 else | |
266 { | |
267 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
268 } | |
269 } | |
270 | |
271 const HttpHeaders& GetHttpHeaders() const | |
272 { | |
273 return headers_; | |
274 } | |
275 | |
276 void SetTimeout(unsigned int seconds) | |
277 { | |
278 timeout_ = seconds; | |
279 } | |
280 | |
281 unsigned int GetTimeout() const | |
282 { | |
283 return timeout_; | |
284 } | |
285 }; | |
286 | |
287 | |
288 | |
289 class NativeOracle : public IOracle | |
290 { | |
291 private: | |
292 class Item : public Orthanc::IDynamicObject | |
293 { | |
294 private: | |
295 const OrthancStone::IObserver& receiver_; | |
296 std::auto_ptr<IOracleCommand> command_; | |
297 | |
298 public: | |
299 Item(const OrthancStone::IObserver& receiver, | |
300 IOracleCommand* command) : | |
301 receiver_(receiver), | |
302 command_(command) | |
303 { | |
304 if (command == NULL) | |
305 { | |
306 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
307 } | |
308 } | |
309 | |
310 const OrthancStone::IObserver& GetReceiver() const | |
311 { | |
312 return receiver_; | |
313 } | |
314 | |
315 const IOracleCommand& GetCommand() const | |
316 { | |
317 assert(command_.get() != NULL); | |
318 return *command_; | |
319 } | |
320 }; | |
321 | |
322 | |
323 enum State | |
324 { | |
325 State_Setup, | |
326 State_Running, | |
327 State_Stopped | |
328 }; | |
329 | |
330 | |
331 IMessageEmitter& emitter_; | |
332 Orthanc::WebServiceParameters orthanc_; | |
333 Orthanc::SharedMessageQueue queue_; | |
334 State state_; | |
335 boost::mutex mutex_; | |
336 std::vector<boost::thread*> workers_; | |
337 | |
338 | |
339 void Execute(const OrthancStone::IObserver& receiver, | |
340 const OrthancApiOracleCommand& command) | |
341 { | |
342 Orthanc::HttpClient client(orthanc_, command.GetUri()); | |
343 client.SetMethod(command.GetMethod()); | |
344 client.SetTimeout(command.GetTimeout()); | |
345 | |
346 if (command.GetMethod() == Orthanc::HttpMethod_Post || | |
347 command.GetMethod() == Orthanc::HttpMethod_Put) | |
348 { | |
349 client.SetBody(command.GetBody()); | |
350 } | |
351 | |
352 { | |
353 const HttpHeaders& headers = command.GetHttpHeaders(); | |
354 for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ ) | |
355 { | |
356 client.AddHeader(it->first, it->second); | |
357 } | |
358 } | |
359 | |
360 std::string answer; | |
361 HttpHeaders answerHeaders; | |
362 | |
363 bool success; | |
364 try | |
365 { | |
366 success = client.Apply(answer, answerHeaders); | |
367 } | |
368 catch (Orthanc::OrthancException& e) | |
369 { | |
370 success = false; | |
371 } | |
372 | |
373 if (success) | |
374 { | |
375 OrthancApiOracleCommand::SuccessMessage message(command, answerHeaders, answer); | |
376 emitter_.EmitMessage(receiver, message); | |
377 } | |
378 else | |
379 { | |
380 OrthancApiOracleCommand::FailureMessage message(command, client.GetLastStatus()); | |
381 emitter_.EmitMessage(receiver, message); | |
382 } | |
383 } | |
384 | |
385 | |
386 | |
387 void Step() | |
388 { | |
389 std::auto_ptr<Orthanc::IDynamicObject> object(queue_.Dequeue(100)); | |
390 | |
391 if (object.get() != NULL) | |
392 { | |
393 const Item& item = dynamic_cast<Item&>(*object); | |
394 | |
395 try | |
396 { | |
397 switch (item.GetCommand().GetType()) | |
398 { | |
399 case IOracleCommand::Type_OrthancApi: | |
400 Execute(item.GetReceiver(), | |
401 dynamic_cast<const OrthancApiOracleCommand&>(item.GetCommand())); | |
402 break; | |
403 | |
404 default: | |
405 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
406 } | |
407 } | |
408 catch (Orthanc::OrthancException& e) | |
409 { | |
410 LOG(ERROR) << "Exception within the oracle: " << e.What(); | |
411 } | |
412 catch (...) | |
413 { | |
414 LOG(ERROR) << "Native exception within the oracle"; | |
415 } | |
416 } | |
417 } | |
418 | |
419 | |
420 static void Worker(NativeOracle* that) | |
421 { | |
422 assert(that != NULL); | |
423 | |
424 for (;;) | |
425 { | |
426 { | |
427 boost::mutex::scoped_lock lock(that->mutex_); | |
428 if (that->state_ != State_Running) | |
429 { | |
430 return; | |
431 } | |
432 } | |
433 | |
434 that->Step(); | |
435 } | |
436 } | |
437 | |
438 | |
439 void StopInternal() | |
440 { | |
441 { | |
442 boost::mutex::scoped_lock lock(mutex_); | |
443 | |
444 if (state_ == State_Setup || | |
445 state_ == State_Stopped) | |
446 { | |
447 return; | |
448 } | |
449 else | |
450 { | |
451 state_ = State_Stopped; | |
452 } | |
453 } | |
454 | |
455 for (size_t i = 0; i < workers_.size(); i++) | |
456 { | |
457 if (workers_[i] != NULL) | |
458 { | |
459 if (workers_[i]->joinable()) | |
460 { | |
461 workers_[i]->join(); | |
462 } | |
463 | |
464 delete workers_[i]; | |
465 } | |
466 } | |
467 } | |
468 | |
469 | |
470 public: | |
471 NativeOracle(IMessageEmitter& emitter) : | |
472 emitter_(emitter), | |
473 state_(State_Setup), | |
474 workers_(4) | |
475 { | |
476 } | |
477 | |
478 virtual ~NativeOracle() | |
479 { | |
480 StopInternal(); | |
481 } | |
482 | |
483 void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc) | |
484 { | |
485 boost::mutex::scoped_lock lock(mutex_); | |
486 | |
487 if (state_ != State_Setup) | |
488 { | |
489 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
490 } | |
491 else | |
492 { | |
493 orthanc_ = orthanc; | |
494 } | |
495 } | |
496 | |
497 void SetWorkersCount(unsigned int count) | |
498 { | |
499 boost::mutex::scoped_lock lock(mutex_); | |
500 | |
501 if (count <= 0) | |
502 { | |
503 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
504 } | |
505 else if (state_ != State_Setup) | |
506 { | |
507 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
508 } | |
509 else | |
510 { | |
511 workers_.resize(count); | |
512 } | |
513 } | |
514 | |
515 void Start() | |
516 { | |
517 boost::mutex::scoped_lock lock(mutex_); | |
518 | |
519 if (state_ != State_Setup) | |
520 { | |
521 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
522 } | |
523 else | |
524 { | |
525 state_ = State_Running; | |
526 | |
527 for (unsigned int i = 0; i < workers_.size(); i++) | |
528 { | |
529 workers_[i] = new boost::thread(Worker, this); | |
530 } | |
531 } | |
532 } | |
533 | |
534 void Stop() | |
535 { | |
536 StopInternal(); | |
537 } | |
538 | |
539 virtual void Schedule(const OrthancStone::IObserver& receiver, | |
540 IOracleCommand* command) | |
541 { | |
542 queue_.Enqueue(new Item(receiver, command)); | |
543 } | |
544 }; | |
545 | |
546 | |
547 | |
548 class NativeApplicationContext : public IMessageEmitter | |
549 { | |
550 private: | |
551 boost::shared_mutex mutex_; | |
552 OrthancStone::MessageBroker broker_; | |
553 OrthancStone::IObservable oracleObservable_; | |
554 | |
555 public: | |
556 NativeApplicationContext() : | |
557 oracleObservable_(broker_) | |
558 { | |
559 } | |
560 | |
561 | |
562 virtual void EmitMessage(const OrthancStone::IObserver& observer, | |
563 const OrthancStone::IMessage& message) | |
564 { | |
565 boost::unique_lock<boost::shared_mutex> lock(mutex_); | |
566 oracleObservable_.EmitMessage(observer, message); | |
567 } | |
568 | |
569 | |
570 class ReaderLock : public boost::noncopyable | |
571 { | |
572 private: | |
573 NativeApplicationContext& that_; | |
574 boost::shared_lock<boost::shared_mutex> lock_; | |
575 | |
576 public: | |
577 ReaderLock(NativeApplicationContext& that) : | |
578 that_(that), | |
579 lock_(that.mutex_) | |
580 { | |
581 } | |
582 }; | |
583 | |
584 | |
585 class WriterLock : public boost::noncopyable | |
586 { | |
587 private: | |
588 NativeApplicationContext& that_; | |
589 boost::unique_lock<boost::shared_mutex> lock_; | |
590 | |
591 public: | |
592 WriterLock(NativeApplicationContext& that) : | |
593 that_(that), | |
594 lock_(that.mutex_) | |
595 { | |
596 } | |
597 | |
598 OrthancStone::MessageBroker& GetBroker() | |
599 { | |
600 return that_.broker_; | |
601 } | |
602 | |
603 OrthancStone::IObservable& GetOracleObservable() | |
604 { | |
605 return that_.oracleObservable_; | |
606 } | |
607 }; | |
608 }; | |
609 | |
610 | |
611 | |
612 class DicomInstanceParameters : public boost::noncopyable | |
613 { | |
614 private: | |
615 Orthanc::DicomImageInformation imageInformation_; | |
616 OrthancStone::SopClassUid sopClassUid_; | |
617 double thickness_; | |
618 double pixelSpacingX_; | |
619 double pixelSpacingY_; | |
620 OrthancStone::CoordinateSystem3D geometry_; | |
621 OrthancStone::Vector frameOffsets_; | |
622 bool isColor_; | |
623 bool hasRescale_; | |
624 double rescaleOffset_; | |
625 double rescaleSlope_; | |
626 bool hasDefaultWindowing_; | |
627 float defaultWindowingCenter_; | |
628 float defaultWindowingWidth_; | |
629 Orthanc::PixelFormat expectedPixelFormat_; | |
630 | |
631 void ComputeDoseOffsets(const Orthanc::DicomMap& dicom) | |
632 { | |
633 // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html | |
634 | |
635 { | |
636 std::string increment; | |
637 | |
638 if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false)) | |
639 { | |
640 Orthanc::Toolbox::ToUpperCase(increment); | |
641 if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag | |
642 { | |
643 LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag"; | |
644 return; | |
645 } | |
646 } | |
647 } | |
648 | |
649 if (!OrthancStone::LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) || | |
650 frameOffsets_.size() < imageInformation_.GetNumberOfFrames()) | |
651 { | |
652 LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)"; | |
653 frameOffsets_.clear(); | |
654 } | |
655 else | |
656 { | |
657 if (frameOffsets_.size() >= 2) | |
658 { | |
659 thickness_ = frameOffsets_[1] - frameOffsets_[0]; | |
660 | |
661 if (thickness_ < 0) | |
662 { | |
663 thickness_ = -thickness_; | |
664 } | |
665 } | |
666 } | |
667 } | |
668 | |
669 public: | |
670 DicomInstanceParameters(const Orthanc::DicomMap& dicom) : | |
671 imageInformation_(dicom) | |
672 { | |
673 if (imageInformation_.GetNumberOfFrames() <= 0) | |
674 { | |
675 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
676 } | |
677 | |
678 std::string s; | |
679 if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false)) | |
680 { | |
681 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
682 } | |
683 else | |
684 { | |
685 sopClassUid_ = OrthancStone::StringToSopClassUid(s); | |
686 } | |
687 | |
688 if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS)) | |
689 { | |
690 thickness_ = 100.0 * std::numeric_limits<double>::epsilon(); | |
691 } | |
692 | |
693 OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom); | |
694 | |
695 std::string position, orientation; | |
696 if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) && | |
697 dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)) | |
698 { | |
699 geometry_ = OrthancStone::CoordinateSystem3D(position, orientation); | |
700 } | |
701 | |
702 if (sopClassUid_ == OrthancStone::SopClassUid_RTDose) | |
703 { | |
704 ComputeDoseOffsets(dicom); | |
705 } | |
706 | |
707 isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 && | |
708 imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2); | |
709 | |
710 double doseGridScaling; | |
711 | |
712 if (dicom.ParseDouble(rescaleOffset_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && | |
713 dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE)) | |
714 { | |
715 hasRescale_ = true; | |
716 } | |
717 else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) | |
718 { | |
719 hasRescale_ = true; | |
720 rescaleOffset_ = 0; | |
721 rescaleSlope_ = doseGridScaling; | |
722 } | |
723 else | |
724 { | |
725 hasRescale_ = false; | |
726 } | |
727 | |
728 OrthancStone::Vector c, w; | |
729 if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) && | |
730 OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) && | |
731 c.size() > 0 && | |
732 w.size() > 0) | |
733 { | |
734 hasDefaultWindowing_ = true; | |
735 defaultWindowingCenter_ = static_cast<float>(c[0]); | |
736 defaultWindowingWidth_ = static_cast<float>(w[0]); | |
737 } | |
738 else | |
739 { | |
740 hasDefaultWindowing_ = false; | |
741 } | |
742 | |
743 if (sopClassUid_ == OrthancStone::SopClassUid_RTDose) | |
744 { | |
745 switch (imageInformation_.GetBitsStored()) | |
746 { | |
747 case 16: | |
748 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; | |
749 break; | |
750 | |
751 case 32: | |
752 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32; | |
753 break; | |
754 | |
755 default: | |
756 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
757 } | |
758 } | |
759 else if (isColor_) | |
760 { | |
761 expectedPixelFormat_ = Orthanc::PixelFormat_RGB24; | |
762 } | |
763 else if (imageInformation_.IsSigned()) | |
764 { | |
765 expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16; | |
766 } | |
767 else | |
768 { | |
769 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; | |
770 } | |
771 } | |
772 | |
773 const Orthanc::DicomImageInformation& GetImageInformation() const | |
774 { | |
775 return imageInformation_; | |
776 } | |
777 | |
778 OrthancStone::SopClassUid GetSopClassUid() const | |
779 { | |
780 return sopClassUid_; | |
781 } | |
782 | |
783 double GetThickness() const | |
784 { | |
785 return thickness_; | |
786 } | |
787 | |
788 double GetPixelSpacingX() const | |
789 { | |
790 return pixelSpacingX_; | |
791 } | |
792 | |
793 double GetPixelSpacingY() const | |
794 { | |
795 return pixelSpacingY_; | |
796 } | |
797 | |
798 const OrthancStone::CoordinateSystem3D& GetGeometry() const | |
799 { | |
800 return geometry_; | |
801 } | |
802 | |
803 OrthancStone::CoordinateSystem3D GetFrameGeometry(unsigned int frame) const | |
804 { | |
805 if (frame >= imageInformation_.GetNumberOfFrames()) | |
806 { | |
807 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
808 } | |
809 | |
810 if (sopClassUid_ == OrthancStone::SopClassUid_RTDose) | |
811 { | |
812 if (frame >= frameOffsets_.size()) | |
813 { | |
814 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
815 } | |
816 | |
817 return OrthancStone::CoordinateSystem3D( | |
818 geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(), | |
819 geometry_.GetAxisX(), | |
820 geometry_.GetAxisY()); | |
821 } | |
822 } | |
823 | |
824 bool FrameContainsPlane(unsigned int frame, | |
825 const OrthancStone::CoordinateSystem3D& plane) const | |
826 { | |
827 if (frame >= imageInformation_.GetNumberOfFrames()) | |
828 { | |
829 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
830 } | |
831 | |
832 OrthancStone::CoordinateSystem3D tmp = geometry_; | |
833 | |
834 if (frame != 0) | |
835 { | |
836 tmp = GetFrameGeometry(frame); | |
837 } | |
838 | |
839 bool opposite; // Ignored | |
840 return (OrthancStone::GeometryToolbox::IsParallelOrOpposite( | |
841 opposite, tmp.GetNormal(), plane.GetNormal()) && | |
842 OrthancStone::LinearAlgebra::IsNear( | |
843 tmp.ProjectAlongNormal(tmp.GetOrigin()), | |
844 tmp.ProjectAlongNormal(plane.GetOrigin()), | |
845 thickness_ / 2.0)); | |
846 } | |
847 | |
848 bool IsColor() const | |
849 { | |
850 return isColor_; | |
851 } | |
852 | |
853 bool HasRescale() const | |
854 { | |
855 return hasRescale_; | |
856 } | |
857 | |
858 double GetRescaleOffset() const | |
859 { | |
860 if (hasRescale_) | |
861 { | |
862 return rescaleOffset_; | |
863 } | |
864 else | |
865 { | |
866 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
867 } | |
868 } | |
869 | |
870 double GetRescaleSlope() const | |
871 { | |
872 if (hasRescale_) | |
873 { | |
874 return rescaleSlope_; | |
875 } | |
876 else | |
877 { | |
878 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
879 } | |
880 } | |
881 | |
882 bool HasDefaultWindowing() const | |
883 { | |
884 return hasDefaultWindowing_; | |
885 } | |
886 | |
887 float GetDefaultWindowingCenter() const | |
888 { | |
889 if (hasDefaultWindowing_) | |
890 { | |
891 return defaultWindowingCenter_; | |
892 } | |
893 else | |
894 { | |
895 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
896 } | |
897 } | |
898 | |
899 float GetDefaultWindowingWidth() const | |
900 { | |
901 if (hasDefaultWindowing_) | |
902 { | |
903 return defaultWindowingWidth_; | |
904 } | |
905 else | |
906 { | |
907 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
908 } | |
909 } | |
910 | |
911 Orthanc::PixelFormat GetExpectedPixelFormat() const | |
912 { | |
913 return expectedPixelFormat_; | |
914 } | |
915 }; | |
916 | |
917 | |
918 class AxialVolumeOrthancLoader : public OrthancStone::IObserver | |
919 { | |
920 private: | |
921 class MessageHandler : public Orthanc::IDynamicObject | |
922 { | |
923 public: | |
924 virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const = 0; | |
925 }; | |
926 | |
927 void Handle(const OrthancApiOracleCommand::SuccessMessage& message) | |
928 { | |
929 dynamic_cast<const MessageHandler&>(message.GetOrigin().GetPayload()).Handle(message); | |
930 } | |
931 | |
932 | |
933 class LoadSeriesGeometryHandler : public MessageHandler | |
934 { | |
935 private: | |
936 AxialVolumeOrthancLoader& that_; | |
937 | |
938 public: | |
939 LoadSeriesGeometryHandler(AxialVolumeOrthancLoader& that) : | |
940 that_(that) | |
941 { | |
942 } | |
943 | |
944 virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const | |
945 { | |
946 Json::Value value; | |
947 message.ParseJsonBody(value); | |
948 | |
949 if (value.type() != Json::objectValue) | |
950 { | |
951 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); | |
952 } | |
953 | |
954 Json::Value::Members instances = value.getMemberNames(); | |
955 | |
956 for (size_t i = 0; i < instances.size(); i++) | |
957 { | |
958 Orthanc::DicomMap dicom; | |
959 dicom.FromDicomAsJson(value[instances[i]]); | |
960 | |
961 DicomInstanceParameters instance(dicom); | |
962 } | |
963 } | |
964 }; | |
965 | |
966 | |
967 class LoadInstanceGeometryHandler : public MessageHandler | |
968 { | |
969 private: | |
970 AxialVolumeOrthancLoader& that_; | |
971 | |
972 public: | |
973 LoadInstanceGeometryHandler(AxialVolumeOrthancLoader& that) : | |
974 that_(that) | |
975 { | |
976 } | |
977 | |
978 virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const | |
979 { | |
980 Json::Value value; | |
981 message.ParseJsonBody(value); | |
982 | |
983 if (value.type() != Json::objectValue) | |
984 { | |
985 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); | |
986 } | |
987 | |
988 Orthanc::DicomMap dicom; | |
989 dicom.FromDicomAsJson(value); | |
990 | |
991 DicomInstanceParameters instance(dicom); | |
992 } | |
993 }; | |
994 | |
995 | |
996 bool active_; | |
997 std::auto_ptr<OrthancStone::ImageBuffer3D> image_; | |
998 | |
999 | |
1000 public: | |
1001 AxialVolumeOrthancLoader(OrthancStone::IObservable& oracle) : | |
1002 IObserver(oracle.GetBroker()), | |
1003 active_(false) | |
1004 { | |
1005 oracle.RegisterObserverCallback( | |
1006 new OrthancStone::Callable<AxialVolumeOrthancLoader, OrthancApiOracleCommand::SuccessMessage> | |
1007 (*this, &AxialVolumeOrthancLoader::Handle)); | |
1008 } | |
1009 | |
1010 void LoadSeries(IOracle& oracle, | |
1011 const std::string& seriesId) | |
1012 { | |
1013 if (active_) | |
1014 { | |
1015 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1016 } | |
1017 | |
1018 active_ = true; | |
1019 | |
1020 std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand); | |
1021 command->SetUri("/series/" + seriesId + "/instances-tags"); | |
1022 command->SetPayload(new LoadSeriesGeometryHandler(*this)); | |
1023 | |
1024 oracle.Schedule(*this, command.release()); | |
1025 } | |
1026 | |
1027 void LoadInstance(IOracle& oracle, | |
1028 const std::string& instanceId) | |
1029 { | |
1030 if (active_) | |
1031 { | |
1032 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1033 } | |
1034 | |
1035 active_ = true; | |
1036 | |
1037 // Tag "3004-000c" is "Grid Frame Offset Vector", which is | |
1038 // mandatory to read RT DOSE, but is too long to be returned by default | |
1039 | |
1040 // TODO => Should be part of a second call if needed | |
1041 | |
1042 std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand); | |
1043 command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3004-000c"); | |
1044 command->SetPayload(new LoadInstanceGeometryHandler(*this)); | |
1045 | |
1046 oracle.Schedule(*this, command.release()); | |
1047 } | |
1048 }; | |
1049 | |
1050 } | |
1051 | |
1052 | |
1053 | |
1054 class Toto : public OrthancStone::IObserver | |
1055 { | |
1056 private: | |
1057 void Handle(const Refactoring::OrthancApiOracleCommand::SuccessMessage& message) | |
1058 { | |
1059 Json::Value v; | |
1060 message.ParseJsonBody(v); | |
1061 | |
1062 printf("ICI [%s]\n", v.toStyledString().c_str()); | |
1063 } | |
1064 | |
1065 void Handle(const Refactoring::OrthancApiOracleCommand::FailureMessage& message) | |
1066 { | |
1067 printf("ERROR %d\n", message.GetHttpStatus()); | |
1068 } | |
1069 | |
1070 public: | |
1071 Toto(OrthancStone::IObservable& oracle) : | |
1072 IObserver(oracle.GetBroker()) | |
1073 { | |
1074 oracle.RegisterObserverCallback | |
1075 (new OrthancStone::Callable | |
1076 <Toto, Refactoring::OrthancApiOracleCommand::SuccessMessage>(*this, &Toto::Handle)); | |
1077 } | |
1078 }; | |
1079 | |
1080 | |
1081 void Run(Refactoring::NativeApplicationContext& context) | |
1082 { | |
1083 std::auto_ptr<Toto> toto; | |
1084 std::auto_ptr<Refactoring::AxialVolumeOrthancLoader> loader1, loader2; | |
1085 | |
1086 { | |
1087 Refactoring::NativeApplicationContext::WriterLock lock(context); | |
1088 toto.reset(new Toto(lock.GetOracleObservable())); | |
1089 loader1.reset(new Refactoring::AxialVolumeOrthancLoader(lock.GetOracleObservable())); | |
1090 loader2.reset(new Refactoring::AxialVolumeOrthancLoader(lock.GetOracleObservable())); | |
1091 } | |
1092 | |
1093 Refactoring::NativeOracle oracle(context); | |
1094 | |
1095 { | |
1096 Orthanc::WebServiceParameters p; | |
1097 //p.SetUrl("http://localhost:8043/"); | |
1098 p.SetCredentials("orthanc", "orthanc"); | |
1099 oracle.SetOrthancParameters(p); | |
1100 } | |
1101 | |
1102 oracle.Start(); | |
1103 | |
1104 { | |
1105 Json::Value v = Json::objectValue; | |
1106 v["Level"] = "Series"; | |
1107 v["Query"] = Json::objectValue; | |
1108 | |
1109 std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand); | |
1110 command->SetMethod(Orthanc::HttpMethod_Post); | |
1111 command->SetUri("/tools/find"); | |
1112 command->SetBody(v); | |
1113 | |
1114 oracle.Schedule(*toto, command.release()); | |
1115 } | |
1116 | |
1117 // 2017-11-17-Anonymized | |
1118 loader1->LoadSeries(oracle, "cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT | |
1119 loader2->LoadInstance(oracle, "41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE | |
1120 | |
1121 boost::this_thread::sleep(boost::posix_time::seconds(1)); | |
1122 | |
1123 oracle.Stop(); | |
1124 } | |
1125 | |
1126 | |
1127 | |
1128 /** | |
1129 * IMPORTANT: The full arguments to "main()" are needed for SDL on | |
1130 * Windows. Otherwise, one gets the linking error "undefined reference | |
1131 * to `SDL_main'". https://wiki.libsdl.org/FAQWindows | |
1132 **/ | |
1133 int main(int argc, char* argv[]) | |
1134 { | |
1135 OrthancStone::StoneInitialize(); | |
1136 Orthanc::Logging::EnableInfoLevel(true); | |
1137 | |
1138 try | |
1139 { | |
1140 Refactoring::NativeApplicationContext context; | |
1141 Run(context); | |
1142 } | |
1143 catch (Orthanc::OrthancException& e) | |
1144 { | |
1145 LOG(ERROR) << "EXCEPTION: " << e.What(); | |
1146 } | |
1147 | |
1148 OrthancStone::StoneFinalize(); | |
1149 | |
1150 return 0; | |
1151 } |