Mercurial > hg > orthanc-stone
comparison Samples/Sdl/Loader.cpp @ 815:df442f1ba0c6
reorganization
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 28 May 2019 21:59:20 +0200 |
parents | aead999345e0 |
children | 270c31978df1 2fd96a637a59 |
comparison
equal
deleted
inserted
replaced
814:aead999345e0 | 815:df442f1ba0c6 |
---|---|
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/Loaders/DicomStructureSetLoader.h" | |
23 #include "../../Framework/Loaders/OrthancMultiframeVolumeLoader.h" | |
22 #include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" | 24 #include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" |
23 #include "../../Framework/Volumes/DicomVolumeImageMPRSlicer.h" | 25 #include "../../Framework/Oracle/SleepOracleCommand.h" |
26 #include "../../Framework/Oracle/ThreadedOracle.h" | |
27 #include "../../Framework/Scene2D/CairoCompositor.h" | |
24 #include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h" | 28 #include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h" |
25 #include "../../Framework/Scene2D/LookupTableStyleConfigurator.h" | 29 #include "../../Framework/Scene2D/LookupTableStyleConfigurator.h" |
26 #include "../../Framework/Oracle/ThreadedOracle.h" | |
27 #include "../../Framework/Oracle/GetOrthancWebViewerJpegCommand.h" | |
28 #include "../../Framework/Oracle/GetOrthancImageCommand.h" | |
29 #include "../../Framework/Oracle/OrthancRestApiCommand.h" | |
30 #include "../../Framework/Oracle/SleepOracleCommand.h" | |
31 #include "../../Framework/Oracle/OracleCommandExceptionMessage.h" | |
32 | |
33 // From Stone | |
34 #include "../../Framework/Loaders/BasicFetchingItemsSorter.h" | |
35 #include "../../Framework/Loaders/BasicFetchingStrategy.h" | |
36 #include "../../Framework/Scene2D/CairoCompositor.h" | |
37 #include "../../Framework/Scene2D/Scene2D.h" | |
38 #include "../../Framework/Scene2D/PolylineSceneLayer.h" | |
39 #include "../../Framework/Scene2D/LookupTableTextureSceneLayer.h" | |
40 #include "../../Framework/StoneInitialization.h" | 30 #include "../../Framework/StoneInitialization.h" |
41 #include "../../Framework/Toolbox/GeometryToolbox.h" | 31 #include "../../Framework/Volumes/VolumeSceneLayerSource.h" |
42 #include "../../Framework/Toolbox/SlicesSorter.h" | 32 #include "../../Framework/Volumes/DicomVolumeImageMPRSlicer.h" |
43 #include "../../Framework/Toolbox/DicomStructureSet.h" | 33 #include "../../Framework/Volumes/DicomVolumeImageReslicer.h" |
44 #include "../../Framework/Volumes/ImageBuffer3D.h" | |
45 #include "../../Framework/Volumes/VolumeImageGeometry.h" | |
46 #include "../../Framework/Volumes/VolumeReslicer.h" | |
47 | 34 |
48 // From Orthanc framework | 35 // From Orthanc framework |
49 #include <Core/DicomFormat/DicomArray.h> | |
50 #include <Core/Images/Image.h> | |
51 #include <Core/Images/ImageProcessing.h> | 36 #include <Core/Images/ImageProcessing.h> |
52 #include <Core/Images/PngWriter.h> | 37 #include <Core/Images/PngWriter.h> |
53 #include <Core/Endianness.h> | |
54 #include <Core/Logging.h> | 38 #include <Core/Logging.h> |
55 #include <Core/OrthancException.h> | 39 #include <Core/OrthancException.h> |
56 #include <Core/SystemToolbox.h> | 40 #include <Core/SystemToolbox.h> |
57 #include <Core/Toolbox.h> | |
58 | |
59 | |
60 #include <EmbeddedResources.h> | |
61 | 41 |
62 | 42 |
63 namespace OrthancStone | 43 namespace OrthancStone |
64 { | 44 { |
65 /** | 45 class NativeApplicationContext : public IMessageEmitter |
66 This class is supplied with Oracle commands and will schedule up to | 46 { |
67 simultaneousDownloads_ of them at the same time, then will schedule the | |
68 rest once slots become available. It is used, a.o., by the | |
69 OrtancMultiframeVolumeLoader class. | |
70 */ | |
71 class LoaderStateMachine : public IObserver | |
72 { | |
73 protected: | |
74 class State : public Orthanc::IDynamicObject | |
75 { | |
76 private: | |
77 LoaderStateMachine& that_; | |
78 | |
79 public: | |
80 State(LoaderStateMachine& that) : | |
81 that_(that) | |
82 { | |
83 } | |
84 | |
85 State(const State& currentState) : | |
86 that_(currentState.that_) | |
87 { | |
88 } | |
89 | |
90 void Schedule(OracleCommandWithPayload* command) const | |
91 { | |
92 that_.Schedule(command); | |
93 } | |
94 | |
95 template <typename T> | |
96 T& GetLoader() const | |
97 { | |
98 return dynamic_cast<T&>(that_); | |
99 } | |
100 | |
101 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) | |
102 { | |
103 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
104 } | |
105 | |
106 virtual void Handle(const GetOrthancImageCommand::SuccessMessage& message) | |
107 { | |
108 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
109 } | |
110 | |
111 virtual void Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message) | |
112 { | |
113 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
114 } | |
115 }; | |
116 | |
117 void Schedule(OracleCommandWithPayload* command) | |
118 { | |
119 std::auto_ptr<OracleCommandWithPayload> protection(command); | |
120 | |
121 if (command == NULL) | |
122 { | |
123 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
124 } | |
125 | |
126 if (!command->HasPayload()) | |
127 { | |
128 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, | |
129 "The payload must contain the next state"); | |
130 } | |
131 | |
132 pendingCommands_.push_back(protection.release()); | |
133 Step(); | |
134 } | |
135 | |
136 void Start() | |
137 { | |
138 if (active_) | |
139 { | |
140 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
141 } | |
142 | |
143 active_ = true; | |
144 | |
145 for (size_t i = 0; i < simultaneousDownloads_; i++) | |
146 { | |
147 Step(); | |
148 } | |
149 } | |
150 | |
151 private: | 47 private: |
152 void Step() | 48 boost::shared_mutex mutex_; |
153 { | 49 MessageBroker broker_; |
154 if (!pendingCommands_.empty() && | 50 IObservable oracleObservable_; |
155 activeCommands_ < simultaneousDownloads_) | |
156 { | |
157 oracle_.Schedule(*this, pendingCommands_.front()); | |
158 pendingCommands_.pop_front(); | |
159 | |
160 activeCommands_++; | |
161 } | |
162 } | |
163 | |
164 void Clear() | |
165 { | |
166 for (PendingCommands::iterator it = pendingCommands_.begin(); | |
167 it != pendingCommands_.end(); ++it) | |
168 { | |
169 delete *it; | |
170 } | |
171 | |
172 pendingCommands_.clear(); | |
173 } | |
174 | |
175 void HandleExceptionMessage(const OracleCommandExceptionMessage& message) | |
176 { | |
177 LOG(ERROR) << "Error in the state machine, stopping all processing"; | |
178 Clear(); | |
179 } | |
180 | |
181 template <typename T> | |
182 void HandleSuccessMessage(const T& message) | |
183 { | |
184 assert(activeCommands_ > 0); | |
185 activeCommands_--; | |
186 | |
187 try | |
188 { | |
189 dynamic_cast<State&>(message.GetOrigin().GetPayload()).Handle(message); | |
190 Step(); | |
191 } | |
192 catch (Orthanc::OrthancException& e) | |
193 { | |
194 LOG(ERROR) << "Error in the state machine, stopping all processing: " << e.What(); | |
195 Clear(); | |
196 } | |
197 } | |
198 | |
199 typedef std::list<IOracleCommand*> PendingCommands; | |
200 | |
201 IOracle& oracle_; | |
202 bool active_; | |
203 unsigned int simultaneousDownloads_; | |
204 PendingCommands pendingCommands_; | |
205 unsigned int activeCommands_; | |
206 | |
207 public: | |
208 LoaderStateMachine(IOracle& oracle, | |
209 IObservable& oracleObservable) : | |
210 IObserver(oracleObservable.GetBroker()), | |
211 oracle_(oracle), | |
212 active_(false), | |
213 simultaneousDownloads_(4), | |
214 activeCommands_(0) | |
215 { | |
216 oracleObservable.RegisterObserverCallback( | |
217 new Callable<LoaderStateMachine, OrthancRestApiCommand::SuccessMessage> | |
218 (*this, &LoaderStateMachine::HandleSuccessMessage)); | |
219 | |
220 oracleObservable.RegisterObserverCallback( | |
221 new Callable<LoaderStateMachine, GetOrthancImageCommand::SuccessMessage> | |
222 (*this, &LoaderStateMachine::HandleSuccessMessage)); | |
223 | |
224 oracleObservable.RegisterObserverCallback( | |
225 new Callable<LoaderStateMachine, GetOrthancWebViewerJpegCommand::SuccessMessage> | |
226 (*this, &LoaderStateMachine::HandleSuccessMessage)); | |
227 | |
228 oracleObservable.RegisterObserverCallback( | |
229 new Callable<LoaderStateMachine, OracleCommandExceptionMessage> | |
230 (*this, &LoaderStateMachine::HandleExceptionMessage)); | |
231 } | |
232 | |
233 virtual ~LoaderStateMachine() | |
234 { | |
235 Clear(); | |
236 } | |
237 | |
238 bool IsActive() const | |
239 { | |
240 return active_; | |
241 } | |
242 | |
243 void SetSimultaneousDownloads(unsigned int count) | |
244 { | |
245 if (active_) | |
246 { | |
247 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
248 } | |
249 else if (count == 0) | |
250 { | |
251 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
252 } | |
253 else | |
254 { | |
255 simultaneousDownloads_ = count; | |
256 } | |
257 } | |
258 }; | |
259 | |
260 | |
261 | |
262 class OrthancMultiframeVolumeLoader : | |
263 public LoaderStateMachine, | |
264 public IObservable | |
265 { | |
266 private: | |
267 class LoadRTDoseGeometry : public State | |
268 { | |
269 private: | |
270 std::auto_ptr<Orthanc::DicomMap> dicom_; | |
271 | |
272 public: | |
273 LoadRTDoseGeometry(OrthancMultiframeVolumeLoader& that, | |
274 Orthanc::DicomMap* dicom) : | |
275 State(that), | |
276 dicom_(dicom) | |
277 { | |
278 if (dicom == NULL) | |
279 { | |
280 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
281 } | |
282 | |
283 } | |
284 | |
285 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) | |
286 { | |
287 // Complete the DICOM tags with just-received "Grid Frame Offset Vector" | |
288 std::string s = Orthanc::Toolbox::StripSpaces(message.GetAnswer()); | |
289 dicom_->SetValue(Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, s, false); | |
290 | |
291 GetLoader<OrthancMultiframeVolumeLoader>().SetGeometry(*dicom_); | |
292 } | |
293 }; | |
294 | |
295 | |
296 static std::string GetSopClassUid(const Orthanc::DicomMap& dicom) | |
297 { | |
298 std::string s; | |
299 if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false)) | |
300 { | |
301 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, | |
302 "DICOM file without SOP class UID"); | |
303 } | |
304 else | |
305 { | |
306 return s; | |
307 } | |
308 } | |
309 | |
310 | |
311 class LoadGeometry : public State | |
312 { | |
313 public: | |
314 LoadGeometry(OrthancMultiframeVolumeLoader& that) : | |
315 State(that) | |
316 { | |
317 } | |
318 | |
319 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) | |
320 { | |
321 OrthancMultiframeVolumeLoader& loader = GetLoader<OrthancMultiframeVolumeLoader>(); | |
322 | |
323 Json::Value body; | |
324 message.ParseJsonBody(body); | |
325 | |
326 if (body.type() != Json::objectValue) | |
327 { | |
328 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); | |
329 } | |
330 | |
331 std::auto_ptr<Orthanc::DicomMap> dicom(new Orthanc::DicomMap); | |
332 dicom->FromDicomAsJson(body); | |
333 | |
334 if (StringToSopClassUid(GetSopClassUid(*dicom)) == SopClassUid_RTDose) | |
335 { | |
336 // Download the "Grid Frame Offset Vector" DICOM tag, that is | |
337 // mandatory for RT-DOSE, but is too long to be returned by default | |
338 | |
339 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); | |
340 command->SetUri("/instances/" + loader.GetInstanceId() + "/content/" + | |
341 Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format()); | |
342 command->SetPayload(new LoadRTDoseGeometry(loader, dicom.release())); | |
343 | |
344 Schedule(command.release()); | |
345 } | |
346 else | |
347 { | |
348 loader.SetGeometry(*dicom); | |
349 } | |
350 } | |
351 }; | |
352 | |
353 | |
354 | |
355 class LoadTransferSyntax : public State | |
356 { | |
357 public: | |
358 LoadTransferSyntax(OrthancMultiframeVolumeLoader& that) : | |
359 State(that) | |
360 { | |
361 } | |
362 | |
363 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) | |
364 { | |
365 GetLoader<OrthancMultiframeVolumeLoader>().SetTransferSyntax(message.GetAnswer()); | |
366 } | |
367 }; | |
368 | |
369 | |
370 class LoadUncompressedPixelData : public State | |
371 { | |
372 public: | |
373 LoadUncompressedPixelData(OrthancMultiframeVolumeLoader& that) : | |
374 State(that) | |
375 { | |
376 } | |
377 | |
378 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) | |
379 { | |
380 GetLoader<OrthancMultiframeVolumeLoader>().SetUncompressedPixelData(message.GetAnswer()); | |
381 } | |
382 }; | |
383 | |
384 | |
385 | |
386 boost::shared_ptr<DicomVolumeImage> volume_; | |
387 std::string instanceId_; | |
388 std::string transferSyntaxUid_; | |
389 | |
390 | |
391 const std::string& GetInstanceId() const | |
392 { | |
393 if (IsActive()) | |
394 { | |
395 return instanceId_; | |
396 } | |
397 else | |
398 { | |
399 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
400 } | |
401 } | |
402 | |
403 | |
404 void ScheduleFrameDownloads() | |
405 { | |
406 if (transferSyntaxUid_.empty() || | |
407 !volume_->HasGeometry()) | |
408 { | |
409 return; | |
410 } | |
411 /* | |
412 1.2.840.10008.1.2 Implicit VR Endian: Default Transfer Syntax for DICOM | |
413 1.2.840.10008.1.2.1 Explicit VR Little Endian | |
414 1.2.840.10008.1.2.2 Explicit VR Big Endian | |
415 | |
416 See https://www.dicomlibrary.com/dicom/transfer-syntax/ | |
417 */ | |
418 if (transferSyntaxUid_ == "1.2.840.10008.1.2" || | |
419 transferSyntaxUid_ == "1.2.840.10008.1.2.1" || | |
420 transferSyntaxUid_ == "1.2.840.10008.1.2.2") | |
421 { | |
422 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); | |
423 command->SetHttpHeader("Accept-Encoding", "gzip"); | |
424 command->SetUri("/instances/" + instanceId_ + "/content/" + | |
425 Orthanc::DICOM_TAG_PIXEL_DATA.Format() + "/0"); | |
426 command->SetPayload(new LoadUncompressedPixelData(*this)); | |
427 Schedule(command.release()); | |
428 } | |
429 else | |
430 { | |
431 throw Orthanc::OrthancException( | |
432 Orthanc::ErrorCode_NotImplemented, | |
433 "No support for multiframe instances with transfer syntax: " + transferSyntaxUid_); | |
434 } | |
435 } | |
436 | |
437 | |
438 void SetTransferSyntax(const std::string& transferSyntax) | |
439 { | |
440 transferSyntaxUid_ = Orthanc::Toolbox::StripSpaces(transferSyntax); | |
441 ScheduleFrameDownloads(); | |
442 } | |
443 | |
444 | |
445 void SetGeometry(const Orthanc::DicomMap& dicom) | |
446 { | |
447 DicomInstanceParameters parameters(dicom); | |
448 volume_->SetDicomParameters(parameters); | |
449 | |
450 Orthanc::PixelFormat format; | |
451 if (!parameters.GetImageInformation().ExtractPixelFormat(format, true)) | |
452 { | |
453 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
454 } | |
455 | |
456 double spacingZ; | |
457 switch (parameters.GetSopClassUid()) | |
458 { | |
459 case SopClassUid_RTDose: | |
460 spacingZ = parameters.GetThickness(); | |
461 break; | |
462 | |
463 default: | |
464 throw Orthanc::OrthancException( | |
465 Orthanc::ErrorCode_NotImplemented, | |
466 "No support for multiframe instances with SOP class UID: " + GetSopClassUid(dicom)); | |
467 } | |
468 | |
469 const unsigned int width = parameters.GetImageInformation().GetWidth(); | |
470 const unsigned int height = parameters.GetImageInformation().GetHeight(); | |
471 const unsigned int depth = parameters.GetImageInformation().GetNumberOfFrames(); | |
472 | |
473 { | |
474 VolumeImageGeometry geometry; | |
475 geometry.SetSize(width, height, depth); | |
476 geometry.SetAxialGeometry(parameters.GetGeometry()); | |
477 geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(), | |
478 parameters.GetPixelSpacingY(), spacingZ); | |
479 volume_->Initialize(geometry, format); | |
480 } | |
481 | |
482 volume_->GetPixelData().Clear(); | |
483 | |
484 ScheduleFrameDownloads(); | |
485 | |
486 BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_)); | |
487 } | |
488 | |
489 | |
490 ORTHANC_FORCE_INLINE | |
491 static void CopyPixel(uint32_t& target, | |
492 const void* source) | |
493 { | |
494 // TODO - check alignement? | |
495 target = le32toh(*reinterpret_cast<const uint32_t*>(source)); | |
496 } | |
497 | |
498 | |
499 template <typename T> | |
500 void CopyPixelData(const std::string& pixelData) | |
501 { | |
502 ImageBuffer3D& target = volume_->GetPixelData(); | |
503 | |
504 const unsigned int bpp = target.GetBytesPerPixel(); | |
505 const unsigned int width = target.GetWidth(); | |
506 const unsigned int height = target.GetHeight(); | |
507 const unsigned int depth = target.GetDepth(); | |
508 | |
509 if (pixelData.size() != bpp * width * height * depth) | |
510 { | |
511 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, | |
512 "The pixel data has not the proper size"); | |
513 } | |
514 | |
515 if (pixelData.empty()) | |
516 { | |
517 return; | |
518 } | |
519 | |
520 const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData.c_str()); | |
521 | |
522 for (unsigned int z = 0; z < depth; z++) | |
523 { | |
524 ImageBuffer3D::SliceWriter writer(target, VolumeProjection_Axial, z); | |
525 | |
526 assert (writer.GetAccessor().GetWidth() == width && | |
527 writer.GetAccessor().GetHeight() == height); | |
528 | |
529 for (unsigned int y = 0; y < height; y++) | |
530 { | |
531 assert(sizeof(T) == Orthanc::GetBytesPerPixel(target.GetFormat())); | |
532 | |
533 T* target = reinterpret_cast<T*>(writer.GetAccessor().GetRow(y)); | |
534 | |
535 for (unsigned int x = 0; x < width; x++) | |
536 { | |
537 CopyPixel(*target, source); | |
538 | |
539 target ++; | |
540 source += bpp; | |
541 } | |
542 } | |
543 } | |
544 } | |
545 | |
546 | |
547 void SetUncompressedPixelData(const std::string& pixelData) | |
548 { | |
549 switch (volume_->GetPixelData().GetFormat()) | |
550 { | |
551 case Orthanc::PixelFormat_Grayscale32: | |
552 CopyPixelData<uint32_t>(pixelData); | |
553 break; | |
554 | |
555 default: | |
556 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
557 } | |
558 | |
559 volume_->IncrementRevision(); | |
560 | |
561 BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_)); | |
562 } | |
563 | |
564 | |
565 public: | |
566 OrthancMultiframeVolumeLoader(const boost::shared_ptr<DicomVolumeImage>& volume, | |
567 IOracle& oracle, | |
568 IObservable& oracleObservable) : | |
569 LoaderStateMachine(oracle, oracleObservable), | |
570 IObservable(oracleObservable.GetBroker()), | |
571 volume_(volume) | |
572 { | |
573 if (volume.get() == NULL) | |
574 { | |
575 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
576 } | |
577 } | |
578 | |
579 | |
580 void LoadInstance(const std::string& instanceId) | |
581 { | |
582 Start(); | |
583 | |
584 instanceId_ = instanceId; | |
585 | |
586 { | |
587 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); | |
588 command->SetHttpHeader("Accept-Encoding", "gzip"); | |
589 command->SetUri("/instances/" + instanceId + "/tags"); | |
590 command->SetPayload(new LoadGeometry(*this)); | |
591 Schedule(command.release()); | |
592 } | |
593 | |
594 { | |
595 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); | |
596 command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax"); | |
597 command->SetPayload(new LoadTransferSyntax(*this)); | |
598 Schedule(command.release()); | |
599 } | |
600 } | |
601 }; | |
602 | |
603 | |
604 /** | |
605 This class is able to supply an extract slice for an arbitrary cutting | |
606 plane through a volume image | |
607 */ | |
608 class DicomVolumeImageReslicer : public IVolumeSlicer | |
609 { | |
610 private: | |
611 class Slice : public IExtractedSlice | |
612 { | |
613 private: | |
614 DicomVolumeImageReslicer& that_; | |
615 CoordinateSystem3D cuttingPlane_; | |
616 | |
617 public: | |
618 Slice(DicomVolumeImageReslicer& that, | |
619 const CoordinateSystem3D& cuttingPlane) : | |
620 that_(that), | |
621 cuttingPlane_(cuttingPlane) | |
622 { | |
623 } | |
624 | |
625 virtual bool IsValid() | |
626 { | |
627 return true; | |
628 } | |
629 | |
630 virtual uint64_t GetRevision() | |
631 { | |
632 return that_.volume_->GetRevision(); | |
633 } | |
634 | |
635 virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, | |
636 const CoordinateSystem3D& cuttingPlane) | |
637 { | |
638 VolumeReslicer& reslicer = that_.reslicer_; | |
639 | |
640 if (configurator == NULL) | |
641 { | |
642 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, | |
643 "Must provide a layer style configurator"); | |
644 } | |
645 | |
646 reslicer.SetOutputFormat(that_.volume_->GetPixelData().GetFormat()); | |
647 reslicer.Apply(that_.volume_->GetPixelData(), | |
648 that_.volume_->GetGeometry(), | |
649 cuttingPlane); | |
650 | |
651 if (reslicer.IsSuccess()) | |
652 { | |
653 std::auto_ptr<TextureBaseSceneLayer> layer | |
654 (configurator->CreateTextureFromDicom(reslicer.GetOutputSlice(), | |
655 that_.volume_->GetDicomParameters())); | |
656 if (layer.get() == NULL) | |
657 { | |
658 return NULL; | |
659 } | |
660 | |
661 double s = reslicer.GetPixelSpacing(); | |
662 layer->SetPixelSpacing(s, s); | |
663 layer->SetOrigin(reslicer.GetOutputExtent().GetX1() + 0.5 * s, | |
664 reslicer.GetOutputExtent().GetY1() + 0.5 * s); | |
665 | |
666 // TODO - Angle!! | |
667 | |
668 return layer.release(); | |
669 } | |
670 else | |
671 { | |
672 return NULL; | |
673 } | |
674 } | |
675 }; | |
676 | |
677 boost::shared_ptr<DicomVolumeImage> volume_; | |
678 VolumeReslicer reslicer_; | |
679 | |
680 public: | |
681 DicomVolumeImageReslicer(const boost::shared_ptr<DicomVolumeImage>& volume) : | |
682 volume_(volume) | |
683 { | |
684 if (volume.get() == NULL) | |
685 { | |
686 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
687 } | |
688 } | |
689 | |
690 ImageInterpolation GetInterpolation() const | |
691 { | |
692 return reslicer_.GetInterpolation(); | |
693 } | |
694 | |
695 void SetInterpolation(ImageInterpolation interpolation) | |
696 { | |
697 reslicer_.SetInterpolation(interpolation); | |
698 } | |
699 | |
700 bool IsFastMode() const | |
701 { | |
702 return reslicer_.IsFastMode(); | |
703 } | |
704 | |
705 void SetFastMode(bool fast) | |
706 { | |
707 reslicer_.EnableFastMode(fast); | |
708 } | |
709 | |
710 virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) | |
711 { | |
712 if (volume_->HasGeometry()) | |
713 { | |
714 return new Slice(*this, cuttingPlane); | |
715 } | |
716 else | |
717 { | |
718 return new IVolumeSlicer::InvalidSlice; | |
719 } | |
720 } | |
721 }; | |
722 | |
723 | |
724 | |
725 class DicomStructureSetLoader : | |
726 public LoaderStateMachine, | |
727 public IVolumeSlicer | |
728 { | |
729 private: | |
730 class AddReferencedInstance : public State | |
731 { | |
732 private: | |
733 std::string instanceId_; | |
734 | |
735 public: | |
736 AddReferencedInstance(DicomStructureSetLoader& that, | |
737 const std::string& instanceId) : | |
738 State(that), | |
739 instanceId_(instanceId) | |
740 { | |
741 } | |
742 | |
743 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) | |
744 { | |
745 Json::Value tags; | |
746 message.ParseJsonBody(tags); | |
747 | |
748 Orthanc::DicomMap dicom; | |
749 dicom.FromDicomAsJson(tags); | |
750 | |
751 DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>(); | |
752 loader.content_->AddReferencedSlice(dicom); | |
753 | |
754 loader.countProcessedInstances_ ++; | |
755 assert(loader.countProcessedInstances_ <= loader.countReferencedInstances_); | |
756 | |
757 if (loader.countProcessedInstances_ == loader.countReferencedInstances_) | |
758 { | |
759 // All the referenced instances have been loaded, finalize the RT-STRUCT | |
760 loader.content_->CheckReferencedSlices(); | |
761 loader.revision_++; | |
762 } | |
763 } | |
764 }; | |
765 | |
766 // State that converts a "SOP Instance UID" to an Orthanc identifier | |
767 class LookupInstance : public State | |
768 { | |
769 private: | |
770 std::string sopInstanceUid_; | |
771 | |
772 public: | |
773 LookupInstance(DicomStructureSetLoader& that, | |
774 const std::string& sopInstanceUid) : | |
775 State(that), | |
776 sopInstanceUid_(sopInstanceUid) | |
777 { | |
778 } | |
779 | |
780 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) | |
781 { | |
782 DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>(); | |
783 | |
784 Json::Value lookup; | |
785 message.ParseJsonBody(lookup); | |
786 | |
787 if (lookup.type() != Json::arrayValue || | |
788 lookup.size() != 1 || | |
789 !lookup[0].isMember("Type") || | |
790 !lookup[0].isMember("Path") || | |
791 lookup[0]["Type"].type() != Json::stringValue || | |
792 lookup[0]["ID"].type() != Json::stringValue || | |
793 lookup[0]["Type"].asString() != "Instance") | |
794 { | |
795 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); | |
796 } | |
797 | |
798 const std::string instanceId = lookup[0]["ID"].asString(); | |
799 | |
800 { | |
801 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); | |
802 command->SetHttpHeader("Accept-Encoding", "gzip"); | |
803 command->SetUri("/instances/" + instanceId + "/tags"); | |
804 command->SetPayload(new AddReferencedInstance(loader, instanceId)); | |
805 Schedule(command.release()); | |
806 } | |
807 } | |
808 }; | |
809 | |
810 class LoadStructure : public State | |
811 { | |
812 public: | |
813 LoadStructure(DicomStructureSetLoader& that) : | |
814 State(that) | |
815 { | |
816 } | |
817 | |
818 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) | |
819 { | |
820 DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>(); | |
821 | |
822 { | |
823 OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer()); | |
824 loader.content_.reset(new DicomStructureSet(dicom)); | |
825 } | |
826 | |
827 std::set<std::string> instances; | |
828 loader.content_->GetReferencedInstances(instances); | |
829 | |
830 loader.countReferencedInstances_ = instances.size(); | |
831 | |
832 for (std::set<std::string>::const_iterator | |
833 it = instances.begin(); it != instances.end(); ++it) | |
834 { | |
835 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); | |
836 command->SetUri("/tools/lookup"); | |
837 command->SetMethod(Orthanc::HttpMethod_Post); | |
838 command->SetBody(*it); | |
839 command->SetPayload(new LookupInstance(loader, *it)); | |
840 Schedule(command.release()); | |
841 } | |
842 } | |
843 }; | |
844 | |
845 | |
846 | |
847 std::auto_ptr<DicomStructureSet> content_; | |
848 uint64_t revision_; | |
849 std::string instanceId_; | |
850 unsigned int countProcessedInstances_; | |
851 unsigned int countReferencedInstances_; | |
852 | |
853 | |
854 class Slice : public IExtractedSlice | |
855 { | |
856 private: | |
857 const DicomStructureSet& content_; | |
858 uint64_t revision_; | |
859 bool isValid_; | |
860 | |
861 public: | |
862 Slice(const DicomStructureSet& content, | |
863 uint64_t revision, | |
864 const CoordinateSystem3D& cuttingPlane) : | |
865 content_(content), | |
866 revision_(revision) | |
867 { | |
868 bool opposite; | |
869 | |
870 const Vector normal = content.GetNormal(); | |
871 isValid_ = ( | |
872 GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) || | |
873 GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) || | |
874 GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY())); | |
875 } | |
876 | |
877 virtual bool IsValid() | |
878 { | |
879 return isValid_; | |
880 } | |
881 | |
882 virtual uint64_t GetRevision() | |
883 { | |
884 return revision_; | |
885 } | |
886 | |
887 virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, | |
888 const CoordinateSystem3D& cuttingPlane) | |
889 { | |
890 assert(isValid_); | |
891 | |
892 std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer); | |
893 layer->SetThickness(2); | |
894 | |
895 for (size_t i = 0; i < content_.GetStructuresCount(); i++) | |
896 { | |
897 const Color& color = content_.GetStructureColor(i); | |
898 | |
899 std::vector< std::vector<DicomStructureSet::PolygonPoint> > polygons; | |
900 | |
901 if (content_.ProjectStructure(polygons, i, cuttingPlane)) | |
902 { | |
903 for (size_t j = 0; j < polygons.size(); j++) | |
904 { | |
905 PolylineSceneLayer::Chain chain; | |
906 chain.resize(polygons[j].size()); | |
907 | |
908 for (size_t k = 0; k < polygons[j].size(); k++) | |
909 { | |
910 chain[k] = ScenePoint2D(polygons[j][k].first, polygons[j][k].second); | |
911 } | |
912 | |
913 layer->AddChain(chain, true /* closed */, color); | |
914 } | |
915 } | |
916 } | |
917 | |
918 return layer.release(); | |
919 } | |
920 }; | |
921 | |
922 public: | |
923 DicomStructureSetLoader(IOracle& oracle, | |
924 IObservable& oracleObservable) : | |
925 LoaderStateMachine(oracle, oracleObservable), | |
926 revision_(0), | |
927 countProcessedInstances_(0), | |
928 countReferencedInstances_(0) | |
929 { | |
930 } | |
931 | |
932 | |
933 void LoadInstance(const std::string& instanceId) | |
934 { | |
935 Start(); | |
936 | |
937 instanceId_ = instanceId; | |
938 | |
939 { | |
940 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); | |
941 command->SetHttpHeader("Accept-Encoding", "gzip"); | |
942 command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3006-0050"); | |
943 command->SetPayload(new LoadStructure(*this)); | |
944 Schedule(command.release()); | |
945 } | |
946 } | |
947 | |
948 virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) | |
949 { | |
950 if (content_.get() == NULL) | |
951 { | |
952 // Geometry is not available yet | |
953 return new IVolumeSlicer::InvalidSlice; | |
954 } | |
955 else | |
956 { | |
957 return new Slice(*content_, revision_, cuttingPlane); | |
958 } | |
959 } | |
960 }; | |
961 | |
962 | |
963 | |
964 class VolumeSceneLayerSource : public boost::noncopyable | |
965 { | |
966 private: | |
967 Scene2D& scene_; | |
968 int layerDepth_; | |
969 boost::shared_ptr<IVolumeSlicer> slicer_; | |
970 std::auto_ptr<ILayerStyleConfigurator> configurator_; | |
971 std::auto_ptr<CoordinateSystem3D> lastPlane_; | |
972 uint64_t lastRevision_; | |
973 uint64_t lastConfiguratorRevision_; | |
974 | |
975 static bool IsSameCuttingPlane(const CoordinateSystem3D& a, | |
976 const CoordinateSystem3D& b) | |
977 { | |
978 // TODO - What if the normal is reversed? | |
979 double distance; | |
980 return (CoordinateSystem3D::ComputeDistance(distance, a, b) && | |
981 LinearAlgebra::IsCloseToZero(distance)); | |
982 } | |
983 | |
984 void ClearLayer() | |
985 { | |
986 scene_.DeleteLayer(layerDepth_); | |
987 lastPlane_.reset(NULL); | |
988 } | |
989 | |
990 public: | |
991 VolumeSceneLayerSource(Scene2D& scene, | |
992 int layerDepth, | |
993 const boost::shared_ptr<IVolumeSlicer>& slicer) : | |
994 scene_(scene), | |
995 layerDepth_(layerDepth), | |
996 slicer_(slicer) | |
997 { | |
998 if (slicer == NULL) | |
999 { | |
1000 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
1001 } | |
1002 } | |
1003 | |
1004 const IVolumeSlicer& GetSlicer() const | |
1005 { | |
1006 return *slicer_; | |
1007 } | |
1008 | |
1009 void RemoveConfigurator() | |
1010 { | |
1011 configurator_.reset(); | |
1012 lastPlane_.reset(); | |
1013 } | |
1014 | |
1015 void SetConfigurator(ILayerStyleConfigurator* configurator) // Takes ownership | |
1016 { | |
1017 if (configurator == NULL) | |
1018 { | |
1019 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
1020 } | |
1021 | |
1022 configurator_.reset(configurator); | |
1023 | |
1024 // Invalidate the layer | |
1025 lastPlane_.reset(NULL); | |
1026 } | |
1027 | |
1028 bool HasConfigurator() const | |
1029 { | |
1030 return configurator_.get() != NULL; | |
1031 } | |
1032 | |
1033 ILayerStyleConfigurator& GetConfigurator() const | |
1034 { | |
1035 if (configurator_.get() == NULL) | |
1036 { | |
1037 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1038 } | |
1039 | |
1040 return *configurator_; | |
1041 } | |
1042 | |
1043 void Update(const CoordinateSystem3D& plane) | |
1044 { | |
1045 assert(slicer_.get() != NULL); | |
1046 std::auto_ptr<IVolumeSlicer::IExtractedSlice> slice(slicer_->ExtractSlice(plane)); | |
1047 | |
1048 if (slice.get() == NULL) | |
1049 { | |
1050 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
1051 } | |
1052 | |
1053 if (!slice->IsValid()) | |
1054 { | |
1055 // The slicer cannot handle this cutting plane: Clear the layer | |
1056 ClearLayer(); | |
1057 } | |
1058 else if (lastPlane_.get() != NULL && | |
1059 IsSameCuttingPlane(*lastPlane_, plane) && | |
1060 lastRevision_ == slice->GetRevision()) | |
1061 { | |
1062 // The content of the slice has not changed: Don't update the | |
1063 // layer content, but possibly update its style | |
1064 | |
1065 if (configurator_.get() != NULL && | |
1066 configurator_->GetRevision() != lastConfiguratorRevision_ && | |
1067 scene_.HasLayer(layerDepth_)) | |
1068 { | |
1069 configurator_->ApplyStyle(scene_.GetLayer(layerDepth_)); | |
1070 } | |
1071 } | |
1072 else | |
1073 { | |
1074 // Content has changed: An update is needed | |
1075 lastPlane_.reset(new CoordinateSystem3D(plane)); | |
1076 lastRevision_ = slice->GetRevision(); | |
1077 | |
1078 std::auto_ptr<ISceneLayer> layer(slice->CreateSceneLayer(configurator_.get(), plane)); | |
1079 if (layer.get() == NULL) | |
1080 { | |
1081 ClearLayer(); | |
1082 } | |
1083 else | |
1084 { | |
1085 if (configurator_.get() != NULL) | |
1086 { | |
1087 lastConfiguratorRevision_ = configurator_->GetRevision(); | |
1088 configurator_->ApplyStyle(*layer); | |
1089 } | |
1090 | |
1091 scene_.SetLayer(layerDepth_, layer.release()); | |
1092 } | |
1093 } | |
1094 } | |
1095 }; | |
1096 | |
1097 | |
1098 | |
1099 | |
1100 | |
1101 class NativeApplicationContext : public IMessageEmitter | |
1102 { | |
1103 private: | |
1104 boost::shared_mutex mutex_; | |
1105 MessageBroker broker_; | |
1106 IObservable oracleObservable_; | |
1107 | 51 |
1108 public: | 52 public: |
1109 NativeApplicationContext() : | 53 NativeApplicationContext() : |
1110 oracleObservable_(broker_) | 54 oracleObservable_(broker_) |
1111 { | 55 { |