comparison Framework/Loaders/DicomStructureSetLoader.cpp @ 1337:b1396be5aa27 broker

Moved the fixed loaders back from the dead
author Benjamin Golinvaux <bgo@osimis.io>
date Fri, 03 Apr 2020 16:13:06 +0200
parents Framework/Deprecated/Loaders/DicomStructureSetLoader.cpp@9b126de2cde2
children 556b4bc19118
comparison
equal deleted inserted replaced
1334:04055b6b9e2c 1337:b1396be5aa27
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 <Core/Toolbox.h>
29
30 #include <algorithm>
31
32 #if 0
33 bool logbgo233 = false;
34 bool logbgo115 = false;
35 #endif
36
37 namespace OrthancStone
38 {
39
40 #if 0
41 void DumpDicomMap(std::ostream& o, const Orthanc::DicomMap& dicomMap)
42 {
43 using namespace std;
44 //ios_base::fmtflags state = o.flags();
45 //o.flags(ios::right | ios::hex);
46 //o << "(" << setfill('0') << setw(4) << tag.GetGroup()
47 // << "," << setw(4) << tag.GetElement() << ")";
48 //o.flags(state);
49 Json::Value val;
50 dicomMap.Serialize(val);
51 o << val;
52 //return o;
53 }
54 #endif
55
56
57 class DicomStructureSetLoader::AddReferencedInstance : public LoaderStateMachine::State
58 {
59 private:
60 std::string instanceId_;
61
62 public:
63 AddReferencedInstance(DicomStructureSetLoader& that,
64 const std::string& instanceId) :
65 State(that),
66 instanceId_(instanceId)
67 {
68 }
69
70 virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
71 {
72 Json::Value tags;
73 message.ParseJsonBody(tags);
74
75 Orthanc::DicomMap dicom;
76 dicom.FromDicomAsJson(tags);
77
78 DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
79
80 loader.content_->AddReferencedSlice(dicom);
81
82 loader.countProcessedInstances_ ++;
83 assert(loader.countProcessedInstances_ <= loader.countReferencedInstances_);
84
85 if (loader.countProcessedInstances_ == loader.countReferencedInstances_)
86 {
87 // All the referenced instances have been loaded, finalize the RT-STRUCT
88 loader.content_->CheckReferencedSlices();
89 loader.revision_++;
90 loader.SetStructuresReady();
91 }
92 }
93 };
94
95
96 // State that converts a "SOP Instance UID" to an Orthanc identifier
97 class DicomStructureSetLoader::LookupInstance : public LoaderStateMachine::State
98 {
99 private:
100 std::string sopInstanceUid_;
101
102 public:
103 LookupInstance(DicomStructureSetLoader& that,
104 const std::string& sopInstanceUid) :
105 State(that),
106 sopInstanceUid_(sopInstanceUid)
107 {
108 }
109
110 virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
111 {
112 #if 0
113 LOG(TRACE) << "DicomStructureSetLoader::LookupInstance::Handle() (SUCCESS)";
114 #endif
115 DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
116
117 Json::Value lookup;
118 message.ParseJsonBody(lookup);
119
120 if (lookup.type() != Json::arrayValue ||
121 lookup.size() != 1 ||
122 !lookup[0].isMember("Type") ||
123 !lookup[0].isMember("Path") ||
124 lookup[0]["Type"].type() != Json::stringValue ||
125 lookup[0]["ID"].type() != Json::stringValue ||
126 lookup[0]["Type"].asString() != "Instance")
127 {
128 std::stringstream msg;
129 msg << "Unknown resource! message.GetAnswer() = " << message.GetAnswer() << " message.GetAnswerHeaders() = ";
130 for (OrthancStone::OrthancRestApiCommand::HttpHeaders::const_iterator it = message.GetAnswerHeaders().begin();
131 it != message.GetAnswerHeaders().end(); ++it)
132 {
133 msg << "\nkey: \"" << it->first << "\" value: \"" << it->second << "\"\n";
134 }
135 const std::string msgStr = msg.str();
136 LOG(ERROR) << msgStr;
137 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
138 }
139
140 const std::string instanceId = lookup[0]["ID"].asString();
141
142 {
143 std::unique_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
144 command->SetHttpHeader("Accept-Encoding", "gzip");
145 std::string uri = "/instances/" + instanceId + "/tags";
146 command->SetUri(uri);
147 command->AcquirePayload(new AddReferencedInstance(loader, instanceId));
148 Schedule(command.release());
149 }
150 }
151 };
152
153
154 class DicomStructureSetLoader::LoadStructure : public LoaderStateMachine::State
155 {
156 public:
157 LoadStructure(DicomStructureSetLoader& that) :
158 State(that)
159 {
160 }
161
162 virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
163 {
164 #if 0
165 if (logbgo115)
166 LOG(TRACE) << "DicomStructureSetLoader::LoadStructure::Handle() (SUCCESS)";
167 #endif
168 DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
169
170 {
171 OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer());
172 loader.content_.reset(new OrthancStone::DicomStructureSet(dicom));
173 size_t structureCount = loader.content_->GetStructuresCount();
174 loader.structureVisibility_.resize(structureCount);
175 bool everythingVisible = false;
176 if ((loader.initiallyVisibleStructures_.size() == 1)
177 && (loader.initiallyVisibleStructures_[0].size() == 1)
178 && (loader.initiallyVisibleStructures_[0][0] == '*'))
179 {
180 everythingVisible = true;
181 }
182
183 for (size_t i = 0; i < structureCount; ++i)
184 {
185 // if a single "*" string is supplied, this means we want everything
186 // to be visible...
187 if(everythingVisible)
188 {
189 loader.structureVisibility_.at(i) = true;
190 }
191 else
192 {
193 // otherwise, we only enable visibility for those structures whose
194 // names are mentioned in the initiallyVisibleStructures_ array
195 const std::string& structureName = loader.content_->GetStructureName(i);
196
197 std::vector<std::string>::iterator foundIt =
198 std::find(
199 loader.initiallyVisibleStructures_.begin(),
200 loader.initiallyVisibleStructures_.end(),
201 structureName);
202 std::vector<std::string>::iterator endIt = loader.initiallyVisibleStructures_.end();
203 if (foundIt != endIt)
204 loader.structureVisibility_.at(i) = true;
205 else
206 loader.structureVisibility_.at(i) = false;
207 }
208 }
209 }
210
211 // Some (admittedly invalid) Dicom files have empty values in the
212 // 0008,1155 tag. We try our best to cope with this.
213 std::set<std::string> instances;
214 std::set<std::string> nonEmptyInstances;
215 loader.content_->GetReferencedInstances(instances);
216 for (std::set<std::string>::const_iterator
217 it = instances.begin(); it != instances.end(); ++it)
218 {
219 std::string instance = Orthanc::Toolbox::StripSpaces(*it);
220 if(instance != "")
221 nonEmptyInstances.insert(instance);
222 }
223
224 loader.countReferencedInstances_ =
225 static_cast<unsigned int>(nonEmptyInstances.size());
226
227 for (std::set<std::string>::const_iterator
228 it = nonEmptyInstances.begin(); it != nonEmptyInstances.end(); ++it)
229 {
230 std::unique_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
231 command->SetUri("/tools/lookup");
232 command->SetMethod(Orthanc::HttpMethod_Post);
233 command->SetBody(*it);
234 command->AcquirePayload(new LookupInstance(loader, *it));
235 Schedule(command.release());
236 }
237 }
238 };
239
240
241 class DicomStructureSetLoader::Slice : public IExtractedSlice
242 {
243 private:
244 const OrthancStone::DicomStructureSet& content_;
245 uint64_t revision_;
246 bool isValid_;
247 std::vector<bool> visibility_;
248
249 public:
250 /**
251 The visibility vector must either:
252 - be empty
253 or
254 - contain the same number of items as the number of structures in the
255 structure set.
256 In the first case (empty vector), all the structures are displayed.
257 In the second case, the visibility of each structure is defined by the
258 content of the vector at the corresponding index.
259 */
260 Slice(const OrthancStone::DicomStructureSet& content,
261 uint64_t revision,
262 const OrthancStone::CoordinateSystem3D& cuttingPlane,
263 std::vector<bool> visibility = std::vector<bool>())
264 : content_(content)
265 , revision_(revision)
266 , visibility_(visibility)
267 {
268 ORTHANC_ASSERT((visibility_.size() == content_.GetStructuresCount())
269 || (visibility_.size() == 0u));
270
271 bool opposite;
272
273 const OrthancStone::Vector normal = content.GetNormal();
274 isValid_ = (
275 OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) ||
276 OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) ||
277 OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY()));
278 }
279
280 virtual bool IsValid()
281 {
282 return isValid_;
283 }
284
285 virtual uint64_t GetRevision()
286 {
287 return revision_;
288 }
289
290 virtual OrthancStone::ISceneLayer* CreateSceneLayer(
291 const OrthancStone::ILayerStyleConfigurator* configurator,
292 const OrthancStone::CoordinateSystem3D& cuttingPlane)
293 {
294 assert(isValid_);
295
296 std::unique_ptr<OrthancStone::PolylineSceneLayer> layer(new OrthancStone::PolylineSceneLayer);
297 layer->SetThickness(2);
298
299 for (size_t i = 0; i < content_.GetStructuresCount(); i++)
300 {
301 if ((visibility_.size() == 0) || visibility_.at(i))
302 {
303 const OrthancStone::Color& color = content_.GetStructureColor(i);
304
305 #ifdef USE_BOOST_UNION_FOR_POLYGONS
306 std::vector< std::vector<OrthancStone::Point2D> > polygons;
307
308 if (content_.ProjectStructure(polygons, i, cuttingPlane))
309 {
310 for (size_t j = 0; j < polygons.size(); j++)
311 {
312 PolylineSceneLayer::Chain chain;
313 chain.resize(polygons[j].size());
314
315 for (size_t k = 0; k < polygons[j].size(); k++)
316 {
317 chain[k] = ScenePoint2D(polygons[j][k].x, polygons[j][k].y);
318 }
319
320 layer->AddChain(chain, true /* closed */, color);
321 }
322 }
323 #else
324 std::vector< std::pair<OrthancStone::Point2D, OrthancStone::Point2D> > segments;
325
326 if (content_.ProjectStructure(segments, i, cuttingPlane))
327 {
328 for (size_t j = 0; j < segments.size(); j++)
329 {
330 OrthancStone::PolylineSceneLayer::Chain chain;
331 chain.resize(2);
332
333 chain[0] = OrthancStone::ScenePoint2D(segments[j].first.x, segments[j].first.y);
334 chain[1] = OrthancStone::ScenePoint2D(segments[j].second.x, segments[j].second.y);
335
336 layer->AddChain(chain, false /* NOT closed */, color);
337 }
338 }
339 #endif
340 }
341 }
342
343 return layer.release();
344 }
345 };
346
347
348 DicomStructureSetLoader::DicomStructureSetLoader(
349 OrthancStone::ILoadersContext& loadersContext)
350 : LoaderStateMachine(loadersContext)
351 , loadersContext_(loadersContext)
352 , revision_(0)
353 , countProcessedInstances_(0)
354 , countReferencedInstances_(0)
355 , structuresReady_(false)
356 {
357 }
358
359
360 boost::shared_ptr<Deprecated::DicomStructureSetLoader> DicomStructureSetLoader::Create(OrthancStone::ILoadersContext& loadersContext)
361 {
362 boost::shared_ptr<DicomStructureSetLoader> obj(
363 new DicomStructureSetLoader(
364 loadersContext));
365 obj->LoaderStateMachine::PostConstructor();
366 return obj;
367
368 }
369
370 void DicomStructureSetLoader::SetStructureDisplayState(size_t structureIndex, bool display)
371 {
372 structureVisibility_.at(structureIndex) = display;
373 revision_++;
374 }
375
376 DicomStructureSetLoader::~DicomStructureSetLoader()
377 {
378 LOG(TRACE) << "DicomStructureSetLoader::~DicomStructureSetLoader()";
379 }
380
381 void DicomStructureSetLoader::LoadInstance(
382 const std::string& instanceId,
383 const std::vector<std::string>& initiallyVisibleStructures)
384 {
385 Start();
386
387 instanceId_ = instanceId;
388 initiallyVisibleStructures_ = initiallyVisibleStructures;
389
390 {
391 std::unique_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
392 command->SetHttpHeader("Accept-Encoding", "gzip");
393
394 std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050";
395
396 command->SetUri(uri);
397 command->AcquirePayload(new LoadStructure(*this));
398 Schedule(command.release());
399 }
400 }
401
402
403 OrthancStone::IVolumeSlicer::IExtractedSlice* DicomStructureSetLoader::ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane)
404 {
405 if (content_.get() == NULL)
406 {
407 // Geometry is not available yet
408 return new OrthancStone::IVolumeSlicer::InvalidSlice;
409 }
410 else
411 {
412 return new Slice(*content_, revision_, cuttingPlane, structureVisibility_);
413 }
414 }
415
416 void DicomStructureSetLoader::SetStructuresReady()
417 {
418 ORTHANC_ASSERT(!structuresReady_);
419 structuresReady_ = true;
420 BroadcastMessage(DicomStructureSetLoader::StructuresReady(*this));
421 }
422
423 bool DicomStructureSetLoader::AreStructuresReady() const
424 {
425 return structuresReady_;
426 }
427
428 }