comparison Framework/Deprecated/Loaders/DicomStructureSetLoader.cpp @ 1225:16738485e457 broker

deprecating DicomStructureSetLoader, OrthancMultiframeVolumeLoader and OrthancSeriesVolumeProgressiveLoader
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sun, 08 Dec 2019 11:45:09 +0100
parents Framework/Loaders/DicomStructureSetLoader.cpp@8e3763d1736a
children 0ca50d275b9a
comparison
equal deleted inserted replaced
1224:37bc7f115f81 1225:16738485e457
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
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 Deprecated
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::auto_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::auto_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::auto_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(OrthancStone::IOracle& oracle,
349 OrthancStone::IObservable& oracleObservable) :
350 LoaderStateMachine(oracle, oracleObservable),
351 revision_(0),
352 countProcessedInstances_(0),
353 countReferencedInstances_(0),
354 structuresReady_(false)
355 {
356 }
357
358
359 void DicomStructureSetLoader::SetStructureDisplayState(size_t structureIndex, bool display)
360 {
361 structureVisibility_.at(structureIndex) = display;
362 revision_++;
363 }
364
365 DicomStructureSetLoader::~DicomStructureSetLoader()
366 {
367 LOG(TRACE) << "DicomStructureSetLoader::~DicomStructureSetLoader()";
368 }
369
370 void DicomStructureSetLoader::LoadInstance(
371 const std::string& instanceId,
372 const std::vector<std::string>& initiallyVisibleStructures)
373 {
374 Start();
375
376 instanceId_ = instanceId;
377 initiallyVisibleStructures_ = initiallyVisibleStructures;
378
379 {
380 std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
381 command->SetHttpHeader("Accept-Encoding", "gzip");
382
383 std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050";
384
385 command->SetUri(uri);
386 command->AcquirePayload(new LoadStructure(*this));
387 Schedule(command.release());
388 }
389 }
390
391
392 OrthancStone::IVolumeSlicer::IExtractedSlice* DicomStructureSetLoader::ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane)
393 {
394 if (content_.get() == NULL)
395 {
396 // Geometry is not available yet
397 return new OrthancStone::IVolumeSlicer::InvalidSlice;
398 }
399 else
400 {
401 return new Slice(*content_, revision_, cuttingPlane, structureVisibility_);
402 }
403 }
404
405 void DicomStructureSetLoader::SetStructuresReady()
406 {
407 ORTHANC_ASSERT(!structuresReady_);
408 structuresReady_ = true;
409 BroadcastMessage(DicomStructureSetLoader::StructuresReady(*this));
410 }
411
412 bool DicomStructureSetLoader::AreStructuresReady() const
413 {
414 return structuresReady_;
415 }
416
417 }