comparison OrthancStone/Sources/Loaders/DicomStructureSetLoader.cpp @ 1512:244ad1e4e76a

reorganization of folders
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 07 Jul 2020 16:21:02 +0200
parents Framework/Loaders/DicomStructureSetLoader.cpp@d8af188ab545
children 85e117739eca
comparison
equal deleted inserted replaced
1511:9dfeee74c1e6 1512:244ad1e4e76a
1 /**
2 * Stone of Orthanc
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Affero General Public License
9 * as published by the Free Software Foundation, either version 3 of
10 * the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/
20
21
22 #include "DicomStructureSetLoader.h"
23
24 #include "../Scene2D/PolylineSceneLayer.h"
25 #include "../StoneException.h"
26 #include "../Toolbox/GeometryToolbox.h"
27
28 #include <Toolbox.h>
29
30 #include <algorithm>
31
32 namespace OrthancStone
33 {
34
35 #if 0
36 void DumpDicomMap(std::ostream& o, const Orthanc::DicomMap& dicomMap)
37 {
38 using namespace std;
39 //ios_base::fmtflags state = o.flags();
40 //o.flags(ios::right | ios::hex);
41 //o << "(" << setfill('0') << setw(4) << tag.GetGroup()
42 // << "," << setw(4) << tag.GetElement() << ")";
43 //o.flags(state);
44 Json::Value val;
45 dicomMap.Serialize(val);
46 o << val;
47 //return o;
48 }
49 #endif
50
51 // implementation of IInstanceLookupHandler that uses Orthanc REST API calls to retrive the
52 // geometry of referenced instances
53 class DicomStructureSetLoader::RestInstanceLookupHandler : public DicomStructureSetLoader::IInstanceLookupHandler,
54 public LoaderStateMachine
55 {
56 public:
57 static boost::shared_ptr<RestInstanceLookupHandler > Create(DicomStructureSetLoader& loader)
58 {
59 boost::shared_ptr<RestInstanceLookupHandler> obj(new RestInstanceLookupHandler(loader));
60 obj->LoaderStateMachine::PostConstructor();
61 return obj;
62 }
63
64 protected:
65 RestInstanceLookupHandler(DicomStructureSetLoader& loader)
66 : LoaderStateMachine(loader.loadersContext_)
67 , loader_(loader)
68 {
69 }
70
71 virtual void RetrieveReferencedSlices(const std::set<std::string>& nonEmptyInstances) ORTHANC_OVERRIDE;
72
73 private:
74 // these subclasses hold the loading state
75 class AddReferencedInstance; // 2nd state
76 class LookupInstance; // 1st state
77
78 DicomStructureSetLoader& loader_;
79 };
80
81 class DicomStructureSetLoader::RestInstanceLookupHandler::AddReferencedInstance : public LoaderStateMachine::State
82 {
83 private:
84 std::string instanceId_;
85
86 public:
87 AddReferencedInstance(DicomStructureSetLoader& that,
88 const std::string& instanceId) :
89 State(that),
90 instanceId_(instanceId)
91 {
92 }
93
94 virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
95 {
96 Json::Value tags;
97 message.ParseJsonBody(tags);
98
99 Orthanc::DicomMap dicom;
100 dicom.FromDicomAsJson(tags);
101
102 DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
103
104 loader.AddReferencedSlice(dicom);
105 }
106 };
107
108
109 // State that converts a "SOP Instance UID" to an Orthanc identifier
110 class DicomStructureSetLoader::RestInstanceLookupHandler::LookupInstance : public LoaderStateMachine::State
111 {
112 private:
113 std::string sopInstanceUid_;
114
115 public:
116 LookupInstance(DicomStructureSetLoader& that,
117 const std::string& sopInstanceUid) :
118 State(that),
119 sopInstanceUid_(sopInstanceUid)
120 {
121 }
122
123 virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
124 {
125 DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
126
127 Json::Value lookup;
128 message.ParseJsonBody(lookup);
129
130 if (lookup.type() != Json::arrayValue ||
131 lookup.size() != 1 ||
132 !lookup[0].isMember("Type") ||
133 !lookup[0].isMember("Path") ||
134 lookup[0]["Type"].type() != Json::stringValue ||
135 lookup[0]["ID"].type() != Json::stringValue ||
136 lookup[0]["Type"].asString() != "Instance")
137 {
138 std::stringstream msg;
139 msg << "Unknown resource! message.GetAnswer() = " << message.GetAnswer() << " message.GetAnswerHeaders() = ";
140 for (OrthancStone::OrthancRestApiCommand::HttpHeaders::const_iterator it = message.GetAnswerHeaders().begin();
141 it != message.GetAnswerHeaders().end(); ++it)
142 {
143 msg << "\nkey: \"" << it->first << "\" value: \"" << it->second << "\"\n";
144 }
145 const std::string msgStr = msg.str();
146 LOG(ERROR) << msgStr;
147 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
148 }
149
150 const std::string instanceId = lookup[0]["ID"].asString();
151
152 {
153 std::unique_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
154 command->SetHttpHeader("Accept-Encoding", "gzip");
155 std::string uri = "/instances/" + instanceId + "/tags";
156 command->SetUri(uri);
157 command->AcquirePayload(new AddReferencedInstance(loader, instanceId));
158 Schedule(command.release());
159 }
160 }
161 };
162
163 void DicomStructureSetLoader::RestInstanceLookupHandler::RetrieveReferencedSlices(
164 const std::set<std::string>& nonEmptyInstances)
165 {
166 for (std::set<std::string>::const_iterator it = nonEmptyInstances.begin();
167 it != nonEmptyInstances.end();
168 ++it)
169 {
170 std::unique_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
171 command->SetUri("/tools/lookup");
172 command->SetMethod(Orthanc::HttpMethod_Post);
173 command->SetBody(*it);
174 command->AcquirePayload(new LookupInstance(loader_, *it));
175 Schedule(command.release());
176 }
177 }
178
179 class DicomStructureSetLoader::LoadStructure : public LoaderStateMachine::State
180 {
181 public:
182 LoadStructure(DicomStructureSetLoader& that) :
183 State(that)
184 {
185 }
186
187 virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
188 {
189 DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
190
191 // Set the actual structure set content
192 {
193 FullOrthancDataset dicom(message.GetAnswer());
194
195 loader.content_.reset(new OrthancStone::DicomStructureSet(dicom));
196 }
197
198 // initialize visibility flags
199 SetDefaultStructureVisibility();
200
201 // retrieve the (non-empty) referenced instances (the CT slices containing the corresponding structures)
202 // Some (admittedly invalid) Dicom files have empty values in the
203 // 0008,1155 tag. We try our best to cope with this.
204 // this is why we use `nonEmptyInstances` and not `instances`
205 std::set<std::string> instances;
206 std::set<std::string> nonEmptyInstances;
207
208 // this traverses the polygon collection for all structures and retrieve the SOPInstanceUID of
209 // the referenced instances
210 loader.content_->GetReferencedInstances(instances);
211
212 for (std::set<std::string>::const_iterator
213 it = instances.begin(); it != instances.end(); ++it)
214 {
215 std::string instance = Orthanc::Toolbox::StripSpaces(*it);
216 if(instance != "")
217 nonEmptyInstances.insert(instance);
218 }
219
220 loader.RetrieveReferencedSlices(nonEmptyInstances);
221 }
222
223 void SetDefaultStructureVisibility()
224 {
225 DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
226
227 size_t structureCount = loader.content_->GetStructuresCount();
228
229 loader.structureVisibility_.resize(structureCount);
230 bool everythingVisible = false;
231 if ((loader.initiallyVisibleStructures_.size() == 1)
232 && (loader.initiallyVisibleStructures_[0].size() == 1)
233 && (loader.initiallyVisibleStructures_[0][0] == '*'))
234 {
235 everythingVisible = true;
236 }
237
238 for (size_t i = 0; i < structureCount; ++i)
239 {
240 // if a single "*" string is supplied, this means we want everything
241 // to be visible...
242 if (everythingVisible)
243 {
244 loader.structureVisibility_.at(i) = true;
245 }
246 else
247 {
248 // otherwise, we only enable visibility for those structures whose
249 // names are mentioned in the initiallyVisibleStructures_ array
250 const std::string& structureName = loader.content_->GetStructureName(i);
251
252 std::vector<std::string>::iterator foundIt =
253 std::find(
254 loader.initiallyVisibleStructures_.begin(),
255 loader.initiallyVisibleStructures_.end(),
256 structureName);
257 std::vector<std::string>::iterator endIt = loader.initiallyVisibleStructures_.end();
258 if (foundIt != endIt)
259 loader.structureVisibility_.at(i) = true;
260 else
261 loader.structureVisibility_.at(i) = false;
262 }
263 }
264 }
265
266 private:
267
268
269 };
270
271
272 class DicomStructureSetLoader::Slice : public IExtractedSlice
273 {
274 private:
275 const OrthancStone::DicomStructureSet& content_;
276 uint64_t revision_;
277 bool isValid_;
278 std::vector<bool> visibility_;
279
280 public:
281 /**
282 The visibility vector must either:
283 - be empty
284 or
285 - contain the same number of items as the number of structures in the
286 structure set.
287 In the first case (empty vector), all the structures are displayed.
288 In the second case, the visibility of each structure is defined by the
289 content of the vector at the corresponding index.
290 */
291 Slice(const OrthancStone::DicomStructureSet& content,
292 uint64_t revision,
293 const OrthancStone::CoordinateSystem3D& cuttingPlane,
294 std::vector<bool> visibility = std::vector<bool>())
295 : content_(content)
296 , revision_(revision)
297 , visibility_(visibility)
298 {
299 ORTHANC_ASSERT((visibility_.size() == content_.GetStructuresCount())
300 || (visibility_.size() == 0u));
301
302 bool opposite;
303
304 const OrthancStone::Vector normal = content.GetNormal();
305 isValid_ = (
306 OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) ||
307 OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) ||
308 OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY()));
309 }
310
311 virtual bool IsValid()
312 {
313 return isValid_;
314 }
315
316 virtual uint64_t GetRevision()
317 {
318 return revision_;
319 }
320
321 virtual OrthancStone::ISceneLayer* CreateSceneLayer(
322 const OrthancStone::ILayerStyleConfigurator* configurator,
323 const OrthancStone::CoordinateSystem3D& cuttingPlane)
324 {
325 assert(isValid_);
326
327 std::unique_ptr<OrthancStone::PolylineSceneLayer> layer(new OrthancStone::PolylineSceneLayer);
328 layer->SetThickness(2);
329
330 for (size_t i = 0; i < content_.GetStructuresCount(); i++)
331 {
332 if ((visibility_.size() == 0) || visibility_.at(i))
333 {
334 const OrthancStone::Color& color = content_.GetStructureColor(i);
335
336 #ifdef USE_BOOST_UNION_FOR_POLYGONS
337 std::vector< std::vector<OrthancStone::Point2D> > polygons;
338
339 if (content_.ProjectStructure(polygons, i, cuttingPlane))
340 {
341 for (size_t j = 0; j < polygons.size(); j++)
342 {
343 PolylineSceneLayer::Chain chain;
344 chain.resize(polygons[j].size());
345
346 for (size_t k = 0; k < polygons[j].size(); k++)
347 {
348 chain[k] = ScenePoint2D(polygons[j][k].x, polygons[j][k].y);
349 }
350
351 layer->AddChain(chain, true /* closed */, color);
352 }
353 }
354 #else
355 std::vector< std::pair<OrthancStone::Point2D, OrthancStone::Point2D> > segments;
356
357 if (content_.ProjectStructure(segments, i, cuttingPlane))
358 {
359 for (size_t j = 0; j < segments.size(); j++)
360 {
361 OrthancStone::PolylineSceneLayer::Chain chain;
362 chain.resize(2);
363
364 chain[0] = OrthancStone::ScenePoint2D(segments[j].first.x, segments[j].first.y);
365 chain[1] = OrthancStone::ScenePoint2D(segments[j].second.x, segments[j].second.y);
366
367 layer->AddChain(chain, false /* NOT closed */, color);
368 }
369 }
370 #endif
371 }
372 }
373
374 return layer.release();
375 }
376 };
377
378
379 DicomStructureSetLoader::DicomStructureSetLoader(
380 OrthancStone::ILoadersContext& loadersContext)
381 : LoaderStateMachine(loadersContext)
382 , loadersContext_(loadersContext)
383 , revision_(0)
384 , countProcessedInstances_(0)
385 , countReferencedInstances_(0)
386 , structuresReady_(false)
387 {
388 // the default handler to retrieve slice geometry is RestInstanceLookupHandler
389 instanceLookupHandler_ = RestInstanceLookupHandler::Create(*this);
390 }
391
392 boost::shared_ptr<OrthancStone::DicomStructureSetLoader> DicomStructureSetLoader::Create(OrthancStone::ILoadersContext& loadersContext)
393 {
394 boost::shared_ptr<DicomStructureSetLoader> obj(
395 new DicomStructureSetLoader(
396 loadersContext));
397 obj->LoaderStateMachine::PostConstructor();
398 return obj;
399 }
400
401 void DicomStructureSetLoader::AddReferencedSlice(const Orthanc::DicomMap& dicom)
402 {
403 content_->AddReferencedSlice(dicom);
404 countProcessedInstances_ ++;
405 assert(countProcessedInstances_ <= countReferencedInstances_);
406
407 revision_++;
408 SetStructuresUpdated();
409
410 if (countProcessedInstances_ == countReferencedInstances_)
411 {
412 // All the referenced instances have been loaded, finalize the RT-STRUCT
413 content_->CheckReferencedSlices();
414 revision_++;
415 SetStructuresReady();
416 }
417 }
418
419 void DicomStructureSetLoader::RetrieveReferencedSlices(const std::set<std::string>& nonEmptyInstances)
420 {
421 // we set the number of referenced instances. This allows to know, in the method above, when we're done
422 countReferencedInstances_ = static_cast<unsigned int>(nonEmptyInstances.size());
423 instanceLookupHandler_->RetrieveReferencedSlices(nonEmptyInstances);
424 }
425
426 void DicomStructureSetLoader::SetStructureDisplayState(size_t structureIndex, bool display)
427 {
428 structureVisibility_.at(structureIndex) = display;
429 revision_++;
430 }
431
432 DicomStructureSetLoader::~DicomStructureSetLoader()
433 {
434 LOG(TRACE) << "DicomStructureSetLoader::~DicomStructureSetLoader()";
435 }
436
437 void DicomStructureSetLoader::LoadInstance(
438 const std::string& instanceId,
439 const std::vector<std::string>& initiallyVisibleStructures)
440 {
441 Start();
442
443 instanceId_ = instanceId;
444 initiallyVisibleStructures_ = initiallyVisibleStructures;
445
446 {
447 std::unique_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
448 command->SetHttpHeader("Accept-Encoding", "gzip");
449
450 std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050";
451
452 command->SetUri(uri);
453 command->AcquirePayload(new LoadStructure(*this));
454 Schedule(command.release());
455 }
456 }
457
458 void DicomStructureSetLoader::LoadInstanceFullVisibility(const std::string& instanceId)
459 {
460 std::vector<std::string> initiallyVisibleStructures;
461 initiallyVisibleStructures.push_back("*"); // wildcard to make all structure sets visible
462 LoadInstance(instanceId, initiallyVisibleStructures);
463 }
464
465 OrthancStone::IVolumeSlicer::IExtractedSlice* DicomStructureSetLoader::ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane)
466 {
467 if (content_.get() == NULL)
468 {
469 // Geometry is not available yet
470 return new OrthancStone::IVolumeSlicer::InvalidSlice;
471 }
472 else
473 {
474 return new Slice(*content_, revision_, cuttingPlane, structureVisibility_);
475 }
476 }
477
478 void DicomStructureSetLoader::SetStructuresUpdated()
479 {
480 BroadcastMessage(DicomStructureSetLoader::StructuresUpdated(*this));
481 }
482
483 void DicomStructureSetLoader::SetStructuresReady()
484 {
485 ORTHANC_ASSERT(!structuresReady_);
486 structuresReady_ = true;
487 BroadcastMessage(DicomStructureSetLoader::StructuresReady(*this));
488 }
489
490 bool DicomStructureSetLoader::AreStructuresReady() const
491 {
492 return structuresReady_;
493 }
494
495 }