Mercurial > hg > orthanc-stone
comparison RenderingPlugin/Sources/Plugin.cpp @ 1885:ddaee6b96501
retrieving rt-struct info
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 14 Jan 2022 19:04:05 +0100 |
parents | 0c6923982fdd |
children | ca89fec8c48f |
comparison
equal
deleted
inserted
replaced
1884:0c6923982fdd | 1885:ddaee6b96501 |
---|---|
18 * You should have received a copy of the GNU General Public License | 18 * You should have received a copy of the GNU General Public License |
19 * along with this program. If not, see <http://www.gnu.org/licenses/>. | 19 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
20 **/ | 20 **/ |
21 | 21 |
22 | 22 |
23 #include "OrthancPluginConnection.h" | |
23 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" | 24 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" |
24 | 25 |
25 #include "../../OrthancStone/Sources/Toolbox/AffineTransform2D.h" | 26 #include "../../OrthancStone/Sources/Toolbox/AffineTransform2D.h" |
26 #include "../../OrthancStone/Sources/Toolbox/DicomInstanceParameters.h" | 27 #include "../../OrthancStone/Sources/Toolbox/DicomInstanceParameters.h" |
28 #include "../../OrthancStone/Sources/Toolbox/DicomStructureSet.h" | |
27 | 29 |
28 #include <EmbeddedResources.h> | 30 #include <EmbeddedResources.h> |
29 | 31 |
30 #include <Images/Image.h> | 32 #include <Images/Image.h> |
31 #include <Images/ImageProcessing.h> | 33 #include <Images/ImageProcessing.h> |
32 #include <Images/NumpyWriter.h> | 34 #include <Images/NumpyWriter.h> |
33 #include <Logging.h> | 35 #include <Logging.h> |
34 #include <SerializationToolbox.h> | 36 #include <SerializationToolbox.h> |
37 #include <Toolbox.h> | |
35 | 38 |
36 #include <boost/math/constants/constants.hpp> | 39 #include <boost/math/constants/constants.hpp> |
40 | |
41 | |
42 class DicomStructureCache : public boost::noncopyable | |
43 { | |
44 private: | |
45 boost::mutex mutex_; | |
46 std::string instanceId_; | |
47 std::unique_ptr<OrthancStone::DicomStructureSet> rtstruct_; | |
48 | |
49 DicomStructureCache() // Singleton design pattern | |
50 { | |
51 } | |
52 | |
53 public: | |
54 void Invalidate(const std::string& instanceId) | |
55 { | |
56 boost::mutex::scoped_lock lock(mutex_); | |
57 | |
58 if (instanceId_ == instanceId) | |
59 { | |
60 rtstruct_.reset(NULL); | |
61 } | |
62 } | |
63 | |
64 static DicomStructureCache& GetSingleton() | |
65 { | |
66 static DicomStructureCache instance; | |
67 return instance; | |
68 } | |
69 | |
70 class Accessor : public boost::noncopyable | |
71 { | |
72 private: | |
73 boost::mutex::scoped_lock lock_; | |
74 std::string instanceId_; | |
75 const OrthancStone::DicomStructureSet* rtstruct_; | |
76 | |
77 public: | |
78 Accessor(DicomStructureCache& that, | |
79 const std::string& instanceId) : | |
80 lock_(that.mutex_), | |
81 instanceId_(instanceId), | |
82 rtstruct_(NULL) | |
83 { | |
84 if (that.instanceId_ == instanceId && | |
85 that.rtstruct_.get() != NULL) | |
86 { | |
87 rtstruct_ = that.rtstruct_.get(); | |
88 } | |
89 else | |
90 { | |
91 try | |
92 { | |
93 OrthancStone::OrthancPluginConnection connection; | |
94 OrthancStone::FullOrthancDataset dataset(connection, "/instances/" + instanceId + "/tags?ignore-length=3006-0050"); | |
95 that.rtstruct_.reset(new OrthancStone::DicomStructureSet(dataset)); | |
96 that.instanceId_ = instanceId; | |
97 rtstruct_ = that.rtstruct_.get(); | |
98 } | |
99 catch (Orthanc::OrthancException&) | |
100 { | |
101 } | |
102 } | |
103 } | |
104 | |
105 const std::string& GetInstanceId() const | |
106 { | |
107 return instanceId_; | |
108 } | |
109 | |
110 bool IsValid() const | |
111 { | |
112 return rtstruct_ != NULL; | |
113 } | |
114 | |
115 const OrthancStone::DicomStructureSet& GetRtStruct() const | |
116 { | |
117 if (IsValid()) | |
118 { | |
119 return *rtstruct_; | |
120 } | |
121 else | |
122 { | |
123 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
124 } | |
125 } | |
126 }; | |
127 }; | |
37 | 128 |
38 | 129 |
39 static Orthanc::PixelFormat Convert(OrthancPluginPixelFormat format) | 130 static Orthanc::PixelFormat Convert(OrthancPluginPixelFormat format) |
40 { | 131 { |
41 switch (format) | 132 switch (format) |
204 Orthanc::NumpyWriter writer; | 295 Orthanc::NumpyWriter writer; |
205 writer.SetCompressed(compress); | 296 writer.SetCompressed(compress); |
206 Orthanc::IImageWriter::WriteToMemory(writer, answer, *modified); | 297 Orthanc::IImageWriter::WriteToMemory(writer, answer, *modified); |
207 | 298 |
208 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, | 299 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, |
209 answer.c_str(), answer.size(), "text/plain"); | 300 answer.c_str(), answer.size(), "application/octet-stream"); |
301 } | |
302 | |
303 | |
304 static bool IsRtStruct(const std::string& instanceId) | |
305 { | |
306 static const char* SOP_CLASS_UID = "0008,0016"; | |
307 static const char* RT_STRUCT_IOD = "1.2.840.10008.5.1.4.1.1.481.3"; | |
308 | |
309 std::string s; | |
310 if (OrthancPlugins::RestApiGetString(s, "/instances/" + instanceId + "/content/" + SOP_CLASS_UID, false) && | |
311 !s.empty()) | |
312 { | |
313 if (s[s.size() - 1] == '\0') // Deal with DICOM padding | |
314 { | |
315 s.resize(s.size() - 1); | |
316 } | |
317 | |
318 return s == RT_STRUCT_IOD; | |
319 } | |
320 else | |
321 { | |
322 return false; | |
323 } | |
324 } | |
325 | |
326 | |
327 static void ListRtStruct(OrthancPluginRestOutput* output, | |
328 const char* url, | |
329 const OrthancPluginHttpRequest* request) | |
330 { | |
331 // This is a quick version of "/tools/find" on "SOPClassUID" (the | |
332 // latter would load all the DICOM files from disk) | |
333 | |
334 static const char* INSTANCES = "Instances"; | |
335 | |
336 Json::Value series; | |
337 OrthancPlugins::RestApiGet(series, "/series?expand", false); | |
338 | |
339 if (series.type() != Json::arrayValue) | |
340 { | |
341 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
342 } | |
343 | |
344 Json::Value answer = Json::arrayValue; | |
345 | |
346 for (Json::Value::ArrayIndex i = 0; i < series.size(); i++) | |
347 { | |
348 if (series[i].type() != Json::objectValue || | |
349 !series[i].isMember(INSTANCES) || | |
350 series[i][INSTANCES].type() != Json::arrayValue) | |
351 { | |
352 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
353 } | |
354 | |
355 const Json::Value& instances = series[i][INSTANCES]; | |
356 | |
357 for (Json::Value::ArrayIndex j = 0; j < instances.size(); j++) | |
358 { | |
359 if (instances[j].type() != Json::stringValue) | |
360 { | |
361 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
362 } | |
363 } | |
364 | |
365 if (instances.size() > 0 && | |
366 IsRtStruct(instances[0].asString())) | |
367 { | |
368 for (Json::Value::ArrayIndex j = 0; j < instances.size(); j++) | |
369 { | |
370 answer.append(instances[j].asString()); | |
371 } | |
372 } | |
373 } | |
374 | |
375 std::string s = answer.toStyledString(); | |
376 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), "application/json"); | |
377 } | |
378 | |
379 | |
380 static void GetRtStruct(OrthancPluginRestOutput* output, | |
381 const char* url, | |
382 const OrthancPluginHttpRequest* request) | |
383 { | |
384 static const char* STRUCTURES = "Structures"; | |
385 static const char* INSTANCES = "Instances"; | |
386 | |
387 DicomStructureCache::Accessor accessor(DicomStructureCache::GetSingleton(), request->groups[0]); | |
388 | |
389 if (!accessor.IsValid()) | |
390 { | |
391 throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem); | |
392 } | |
393 | |
394 Json::Value answer; | |
395 answer[STRUCTURES] = Json::arrayValue; | |
396 | |
397 for (size_t i = 0; i < accessor.GetRtStruct().GetStructuresCount(); i++) | |
398 { | |
399 Json::Value color = Json::arrayValue; | |
400 color.append(accessor.GetRtStruct().GetStructureColor(i).GetRed()); | |
401 color.append(accessor.GetRtStruct().GetStructureColor(i).GetGreen()); | |
402 color.append(accessor.GetRtStruct().GetStructureColor(i).GetBlue()); | |
403 | |
404 Json::Value structure; | |
405 structure["Name"] = accessor.GetRtStruct().GetStructureName(i); | |
406 structure["Interpretation"] = accessor.GetRtStruct().GetStructureInterpretation(i); | |
407 structure["Color"] = color; | |
408 | |
409 answer[STRUCTURES].append(structure); | |
410 } | |
411 | |
412 std::set<std::string> sopInstanceUids; | |
413 accessor.GetRtStruct().GetReferencedInstances(sopInstanceUids); | |
414 | |
415 answer[INSTANCES] = Json::arrayValue; | |
416 for (std::set<std::string>::const_iterator it = sopInstanceUids.begin(); it != sopInstanceUids.end(); ++it) | |
417 { | |
418 OrthancPlugins::OrthancString s; | |
419 s.Assign(OrthancPluginLookupInstance(OrthancPlugins::GetGlobalContext(), it->c_str())); | |
420 | |
421 std::string t; | |
422 s.ToString(t); | |
423 | |
424 answer[INSTANCES].append(t); | |
425 } | |
426 | |
427 std::string s = answer.toStyledString(); | |
428 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), "application/json"); | |
429 } | |
430 | |
431 | |
432 static void RenderRtStruct(OrthancPluginRestOutput* output, | |
433 const char* url, | |
434 const OrthancPluginHttpRequest* request) | |
435 { | |
436 static const char* MAIN_DICOM_TAGS = "MainDicomTags"; | |
437 | |
438 DicomStructureCache::Accessor accessor(DicomStructureCache::GetSingleton(), request->groups[0]); | |
439 | |
440 if (!accessor.IsValid()) | |
441 { | |
442 throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem); | |
443 } | |
444 | |
445 std::string structureName; | |
446 std::string instanceId; | |
447 | |
448 for (uint32_t i = 0; i < request->getCount; i++) | |
449 { | |
450 std::string key(request->getKeys[i]); | |
451 std::string value(request->getValues[i]); | |
452 | |
453 if (key == "structure") | |
454 { | |
455 structureName = value; | |
456 } | |
457 else if (key == "instance") | |
458 { | |
459 instanceId = value; | |
460 } | |
461 else | |
462 { | |
463 LOG(WARNING) << "Unsupported option: " << key; | |
464 } | |
465 } | |
466 | |
467 if (structureName.empty()) | |
468 { | |
469 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, | |
470 "Missing option \"structure\" to provide the structure name"); | |
471 } | |
472 | |
473 if (instanceId.empty()) | |
474 { | |
475 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, | |
476 "Missing option \"instance\" to provide the Orthanc identifier of the instance of interest"); | |
477 } | |
478 | |
479 size_t structureIndex; | |
480 bool found = false; | |
481 for (size_t i = 0; i < accessor.GetRtStruct().GetStructuresCount(); i++) | |
482 { | |
483 if (accessor.GetRtStruct().GetStructureName(i) == structureName) | |
484 { | |
485 structureIndex = i; | |
486 found = true; | |
487 break; | |
488 } | |
489 } | |
490 | |
491 if (!found) | |
492 { | |
493 throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, | |
494 "Unknown structure name: " + structureName); | |
495 } | |
496 | |
497 Json::Value instance; | |
498 if (!OrthancPlugins::RestApiGet(instance, "/instances/" + instanceId, false)) | |
499 { | |
500 throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, | |
501 "Unknown instance with Orthanc ID: " + instanceId); | |
502 } | |
503 | |
504 if (instance.type() != Json::objectValue || | |
505 !instance.isMember(MAIN_DICOM_TAGS) || | |
506 instance[MAIN_DICOM_TAGS].type() != Json::objectValue) | |
507 { | |
508 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
509 } | |
510 | |
511 std::string sopInstanceUid = Orthanc::SerializationToolbox::ReadString(instance[MAIN_DICOM_TAGS], "SOPInstanceUID"); | |
512 | |
513 | |
514 std::list< std::vector<OrthancStone::Vector> > p; | |
515 accessor.GetRtStruct().GetStructurePoints(p, structureIndex, sopInstanceUid); | |
516 | |
517 std::string s = "hello"; | |
518 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), "application/json"); | |
519 } | |
520 | |
521 | |
522 OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, | |
523 OrthancPluginResourceType resourceType, | |
524 const char* resourceId) | |
525 { | |
526 switch (changeType) | |
527 { | |
528 case OrthancPluginChangeType_Deleted: | |
529 if (resourceType == OrthancPluginResourceType_Instance) | |
530 { | |
531 DicomStructureCache::GetSingleton().Invalidate(resourceId); | |
532 } | |
533 | |
534 break; | |
535 | |
536 default: | |
537 break; | |
538 } | |
539 | |
540 return OrthancPluginErrorCode_Success; | |
210 } | 541 } |
211 | 542 |
212 | 543 |
213 extern "C" | 544 extern "C" |
214 { | 545 { |
236 } | 567 } |
237 | 568 |
238 try | 569 try |
239 { | 570 { |
240 OrthancPlugins::RegisterRestCallback<RenderNumpyFrame>("/stone/instances/([^/]+)/frames/([0-9]+)/numpy", true); | 571 OrthancPlugins::RegisterRestCallback<RenderNumpyFrame>("/stone/instances/([^/]+)/frames/([0-9]+)/numpy", true); |
572 OrthancPlugins::RegisterRestCallback<ListRtStruct>("/stone/rt-struct", true); | |
573 OrthancPlugins::RegisterRestCallback<GetRtStruct>("/stone/rt-struct/([^/]+)/info", true); | |
574 OrthancPlugins::RegisterRestCallback<RenderRtStruct>("/stone/rt-struct/([^/]+)/numpy", true); | |
575 OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); | |
241 } | 576 } |
242 catch (...) | 577 catch (...) |
243 { | 578 { |
244 OrthancPlugins::LogError("Exception while initializing the Stone Web viewer plugin"); | 579 OrthancPlugins::LogError("Exception while initializing the Stone Web viewer plugin"); |
245 return -1; | 580 return -1; |