Mercurial > hg > orthanc-stone
annotate Framework/Toolbox/OrthancSeriesLoader.cpp @ 120:063f7f3d9f14 wasm
fix 3d locations of the doses
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 04 Oct 2017 15:51:34 +0200 |
parents | a4d0b6c82b29 |
children |
rev | line source |
---|---|
0 | 1 /** |
2 * Stone of Orthanc | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
40
7207a407bcd8
shared copyright with osimis
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
35
diff
changeset
|
5 * Copyright (C) 2017 Osimis, Belgium |
0 | 6 * |
7 * This program is free software: you can redistribute it and/or | |
47 | 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. | |
0 | 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 | |
47 | 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 | |
0 | 18 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 **/ | |
20 | |
21 | |
22 #include "OrthancSeriesLoader.h" | |
23 | |
35 | 24 #include "../Toolbox/MessagingToolbox.h" |
113
2eca030792aa
using the Orthanc Framework
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
110
diff
changeset
|
25 |
2eca030792aa
using the Orthanc Framework
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
110
diff
changeset
|
26 #include <Core/Images/Image.h> |
2eca030792aa
using the Orthanc Framework
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
110
diff
changeset
|
27 #include <Core/Images/ImageProcessing.h> |
2eca030792aa
using the Orthanc Framework
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
110
diff
changeset
|
28 #include <Core/Logging.h> |
2eca030792aa
using the Orthanc Framework
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
110
diff
changeset
|
29 #include <Core/OrthancException.h> |
2eca030792aa
using the Orthanc Framework
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
110
diff
changeset
|
30 #include <Plugins/Samples/Common/FullOrthancDataset.h> |
2eca030792aa
using the Orthanc Framework
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
110
diff
changeset
|
31 |
0 | 32 #include "DicomFrameConverter.h" |
33 | |
34 namespace OrthancStone | |
35 { | |
36 class OrthancSeriesLoader::Slice : public boost::noncopyable | |
37 { | |
38 private: | |
110
53025eecbc95
renamed SliceGeometry as CoordinateSystem3D
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
67
diff
changeset
|
39 std::string instanceId_; |
53025eecbc95
renamed SliceGeometry as CoordinateSystem3D
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
67
diff
changeset
|
40 CoordinateSystem3D geometry_; |
53025eecbc95
renamed SliceGeometry as CoordinateSystem3D
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
67
diff
changeset
|
41 double projectionAlongNormal_; |
0 | 42 |
43 public: | |
44 Slice(const std::string& instanceId, | |
45 const std::string& imagePositionPatient, | |
46 const std::string& imageOrientationPatient) : | |
47 instanceId_(instanceId), | |
48 geometry_(imagePositionPatient, imageOrientationPatient) | |
49 { | |
50 } | |
51 | |
52 const std::string GetInstanceId() const | |
53 { | |
54 return instanceId_; | |
55 } | |
56 | |
110
53025eecbc95
renamed SliceGeometry as CoordinateSystem3D
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
67
diff
changeset
|
57 const CoordinateSystem3D& GetGeometry() const |
0 | 58 { |
59 return geometry_; | |
60 } | |
61 | |
62 void SetNormal(const Vector& normal) | |
63 { | |
64 projectionAlongNormal_ = boost::numeric::ublas::inner_prod(geometry_.GetOrigin(), normal); | |
65 } | |
66 | |
67 double GetProjectionAlongNormal() const | |
68 { | |
69 return projectionAlongNormal_; | |
70 } | |
71 }; | |
72 | |
73 | |
74 class OrthancSeriesLoader::SetOfSlices : public boost::noncopyable | |
75 { | |
76 private: | |
77 std::vector<Slice*> slices_; | |
78 | |
79 struct Comparator | |
80 { | |
81 bool operator() (const Slice* const a, | |
82 const Slice* const b) const | |
83 { | |
84 return a->GetProjectionAlongNormal() < b->GetProjectionAlongNormal(); | |
85 } | |
86 }; | |
87 | |
88 public: | |
89 ~SetOfSlices() | |
90 { | |
91 for (size_t i = 0; i < slices_.size(); i++) | |
92 { | |
93 assert(slices_[i] != NULL); | |
94 delete slices_[i]; | |
95 } | |
96 } | |
97 | |
98 void Reserve(size_t size) | |
99 { | |
100 slices_.reserve(size); | |
101 } | |
102 | |
103 void AddSlice(const std::string& instanceId, | |
104 const std::string& imagePositionPatient, | |
105 const std::string& imageOrientationPatient) | |
106 { | |
107 slices_.push_back(new Slice(instanceId, imagePositionPatient, imageOrientationPatient)); | |
108 } | |
109 | |
110 size_t GetSliceCount() const | |
111 { | |
112 return slices_.size(); | |
113 } | |
114 | |
115 const Slice& GetSlice(size_t index) const | |
116 { | |
117 assert(slices_[index] != NULL); | |
118 return *slices_[index]; | |
119 } | |
120 | |
121 void Sort(const Vector& normal) | |
122 { | |
123 for (size_t i = 0; i < slices_.size(); i++) | |
124 { | |
125 slices_[i]->SetNormal(normal); | |
126 } | |
127 | |
128 Comparator comparator; | |
129 std::sort(slices_.begin(), slices_.end(), comparator); | |
130 } | |
131 | |
31
9aace933cb64
sharing code with the Orthanc core
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
16
diff
changeset
|
132 void LoadSeriesFast(OrthancPlugins::IOrthancConnection& orthanc, |
0 | 133 const std::string& series) |
134 { | |
135 // Retrieve the orientation of this series | |
136 Json::Value info; | |
137 MessagingToolbox::RestApiGet(info, orthanc, "/series/" + series); | |
138 | |
139 if (info.type() != Json::objectValue || | |
140 !info.isMember("MainDicomTags") || | |
141 info["MainDicomTags"].type() != Json::objectValue || | |
142 !info["MainDicomTags"].isMember("ImageOrientationPatient") || | |
143 info["MainDicomTags"]["ImageOrientationPatient"].type() != Json::stringValue) | |
144 { | |
145 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
146 } | |
147 | |
148 std::string imageOrientationPatient = info["MainDicomTags"]["ImageOrientationPatient"].asString(); | |
149 | |
150 | |
151 // Retrieve the Orthanc ID of all the instances of this series | |
152 Json::Value instances; | |
153 MessagingToolbox::RestApiGet(instances, orthanc, "/series/" + series + "/instances"); | |
154 | |
155 if (instances.type() != Json::arrayValue) | |
156 { | |
157 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
158 } | |
159 | |
160 if (instances.size() == 0) | |
161 { | |
162 LOG(ERROR) << "This series is empty"; | |
163 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); | |
164 } | |
165 | |
166 | |
167 // Retrieve the DICOM tags of all the instances | |
168 std::vector<std::string> instancesId; | |
169 | |
170 instancesId.resize(instances.size()); | |
171 Reserve(instances.size()); | |
172 | |
173 for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++) | |
174 { | |
175 if (instances[i].type() != Json::objectValue || | |
176 !instances[i].isMember("ID") || | |
177 !instances[i].isMember("MainDicomTags") || | |
178 instances[i]["ID"].type() != Json::stringValue || | |
179 instances[i]["MainDicomTags"].type() != Json::objectValue || | |
180 !instances[i]["MainDicomTags"].isMember("ImagePositionPatient") || | |
181 instances[i]["MainDicomTags"]["ImagePositionPatient"].type() != Json::stringValue) | |
182 { | |
183 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
184 } | |
185 else | |
186 { | |
187 instancesId[i] = instances[i]["ID"].asString(); | |
188 AddSlice(instancesId[i], | |
189 instances[i]["MainDicomTags"]["ImagePositionPatient"].asString(), | |
190 imageOrientationPatient); | |
191 } | |
192 } | |
193 | |
194 assert(GetSliceCount() == instances.size()); | |
195 } | |
196 | |
197 | |
31
9aace933cb64
sharing code with the Orthanc core
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
16
diff
changeset
|
198 void LoadSeriesSafe(OrthancPlugins::IOrthancConnection& orthanc, |
0 | 199 const std::string& seriesId) |
200 { | |
201 Json::Value series; | |
202 MessagingToolbox::RestApiGet(series, orthanc, "/series/" + seriesId + "/instances-tags?simplify"); | |
203 | |
204 if (series.type() != Json::objectValue) | |
205 { | |
206 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
207 } | |
208 | |
209 if (series.size() == 0) | |
210 { | |
211 LOG(ERROR) << "This series is empty"; | |
212 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); | |
213 } | |
214 | |
215 Json::Value::Members instances = series.getMemberNames(); | |
216 | |
217 Reserve(instances.size()); | |
218 | |
219 for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++) | |
220 { | |
221 const Json::Value& tags = series[instances[i]]; | |
222 | |
223 if (tags.type() != Json::objectValue || | |
224 !tags.isMember("ImagePositionPatient") || | |
225 !tags.isMember("ImageOrientationPatient") || | |
226 tags["ImagePositionPatient"].type() != Json::stringValue || | |
227 tags["ImageOrientationPatient"].type() != Json::stringValue) | |
228 { | |
229 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
230 } | |
231 else | |
232 { | |
233 AddSlice(instances[i], | |
234 tags["ImagePositionPatient"].asString(), | |
235 tags["ImageOrientationPatient"].asString()); | |
236 } | |
237 } | |
238 | |
239 assert(GetSliceCount() == instances.size()); | |
240 } | |
241 | |
242 | |
243 void SelectNormal(Vector& normal) const | |
244 { | |
245 std::vector<Vector> normalCandidates; | |
246 std::vector<unsigned int> normalCount; | |
247 | |
248 bool found = false; | |
249 | |
250 for (size_t i = 0; !found && i < GetSliceCount(); i++) | |
251 { | |
252 const Vector& normal = GetSlice(i).GetGeometry().GetNormal(); | |
253 | |
254 bool add = true; | |
255 for (size_t j = 0; add && j < normalCandidates.size(); j++) // (*) | |
256 { | |
257 if (GeometryToolbox::IsParallel(normal, normalCandidates[j])) | |
258 { | |
259 normalCount[j] += 1; | |
260 add = false; | |
261 } | |
262 } | |
263 | |
264 if (add) | |
265 { | |
266 if (normalCount.size() > 2) | |
267 { | |
268 // To get linear-time complexity in (*). This heuristics | |
269 // allows the series to have one single frame that is | |
270 // not parallel to the others (such a frame could be a | |
271 // generated preview) | |
272 found = false; | |
273 } | |
274 else | |
275 { | |
276 normalCandidates.push_back(normal); | |
277 normalCount.push_back(1); | |
278 } | |
279 } | |
280 } | |
281 | |
282 for (size_t i = 0; !found && i < normalCandidates.size(); i++) | |
283 { | |
284 unsigned int count = normalCount[i]; | |
285 if (count == GetSliceCount() || | |
286 count + 1 == GetSliceCount()) | |
287 { | |
288 normal = normalCandidates[i]; | |
289 found = true; | |
290 } | |
291 } | |
292 | |
293 if (!found) | |
294 { | |
295 LOG(ERROR) << "Cannot select a normal that is shared by most of the slices of this series"; | |
296 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
297 } | |
298 } | |
299 | |
300 | |
301 void FilterNormal(const Vector& normal) | |
302 { | |
303 size_t pos = 0; | |
304 | |
305 for (size_t i = 0; i < slices_.size(); i++) | |
306 { | |
307 if (GeometryToolbox::IsParallel(normal, slices_[i]->GetGeometry().GetNormal())) | |
308 { | |
309 // This slice is compatible with the selected normal | |
310 slices_[pos] = slices_[i]; | |
311 pos += 1; | |
312 } | |
313 else | |
314 { | |
315 delete slices_[i]; | |
316 slices_[i] = NULL; | |
317 } | |
318 } | |
319 | |
320 slices_.resize(pos); | |
321 } | |
322 }; | |
323 | |
324 | |
325 | |
31
9aace933cb64
sharing code with the Orthanc core
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
16
diff
changeset
|
326 OrthancSeriesLoader::OrthancSeriesLoader(OrthancPlugins::IOrthancConnection& orthanc, |
0 | 327 const std::string& series) : |
328 orthanc_(orthanc), | |
329 slices_(new SetOfSlices) | |
330 { | |
331 /** | |
67
acb60cbb8301
refactoring SeriesLoader
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
47
diff
changeset
|
332 * The function "LoadSeriesFast()" might not behave properly if |
0 | 333 * some slice has some outsider value for its normal, which |
334 * happens sometimes on reprojected series (e.g. coronal and | |
335 * sagittal of Delphine). Don't use it. | |
336 **/ | |
337 | |
338 slices_->LoadSeriesSafe(orthanc, series); | |
339 | |
340 Vector normal; | |
341 slices_->SelectNormal(normal); | |
342 slices_->FilterNormal(normal); | |
343 slices_->Sort(normal); | |
344 | |
345 if (slices_->GetSliceCount() == 0) // Sanity check | |
346 { | |
347 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
348 } | |
349 | |
350 for (size_t i = 0; i < slices_->GetSliceCount(); i++) | |
351 { | |
352 assert(GeometryToolbox::IsParallel(normal, slices_->GetSlice(i).GetGeometry().GetNormal())); | |
353 geometry_.AddSlice(slices_->GetSlice(i).GetGeometry()); | |
354 } | |
355 | |
32 | 356 std::string uri = "/instances/" + slices_->GetSlice(0).GetInstanceId() + "/tags"; |
357 | |
358 OrthancPlugins::FullOrthancDataset dataset(orthanc_, uri); | |
359 | |
118
a4d0b6c82b29
using Orthanc::DicomMap instead of OrthancPlugins::DicomDatasetReader
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
113
diff
changeset
|
360 Orthanc::DicomMap dicom; |
a4d0b6c82b29
using Orthanc::DicomMap instead of OrthancPlugins::DicomDatasetReader
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
113
diff
changeset
|
361 MessagingToolbox::ConvertDataset(dicom, dataset); |
a4d0b6c82b29
using Orthanc::DicomMap instead of OrthancPlugins::DicomDatasetReader
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
113
diff
changeset
|
362 |
a4d0b6c82b29
using Orthanc::DicomMap instead of OrthancPlugins::DicomDatasetReader
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
113
diff
changeset
|
363 if (!dicom.ParseUnsignedInteger32(width_, Orthanc::DICOM_TAG_COLUMNS) || |
a4d0b6c82b29
using Orthanc::DicomMap instead of OrthancPlugins::DicomDatasetReader
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
113
diff
changeset
|
364 !dicom.ParseUnsignedInteger32(height_, Orthanc::DICOM_TAG_ROWS)) |
0 | 365 { |
366 throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentTag); | |
367 } | |
368 | |
369 DicomFrameConverter converter; | |
118
a4d0b6c82b29
using Orthanc::DicomMap instead of OrthancPlugins::DicomDatasetReader
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
113
diff
changeset
|
370 converter.ReadParameters(dicom); |
0 | 371 format_ = converter.GetExpectedPixelFormat(); |
372 } | |
373 | |
374 | |
32 | 375 OrthancPlugins::IDicomDataset* OrthancSeriesLoader::DownloadDicom(size_t index) |
0 | 376 { |
32 | 377 std::string uri = "/instances/" + slices_->GetSlice(index).GetInstanceId() + "/tags"; |
0 | 378 |
32 | 379 std::auto_ptr<OrthancPlugins::IDicomDataset> dataset(new OrthancPlugins::FullOrthancDataset(orthanc_, uri)); |
380 | |
118
a4d0b6c82b29
using Orthanc::DicomMap instead of OrthancPlugins::DicomDatasetReader
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
113
diff
changeset
|
381 Orthanc::DicomMap dicom; |
a4d0b6c82b29
using Orthanc::DicomMap instead of OrthancPlugins::DicomDatasetReader
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
113
diff
changeset
|
382 MessagingToolbox::ConvertDataset(dicom, *dataset); |
a4d0b6c82b29
using Orthanc::DicomMap instead of OrthancPlugins::DicomDatasetReader
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
113
diff
changeset
|
383 |
a4d0b6c82b29
using Orthanc::DicomMap instead of OrthancPlugins::DicomDatasetReader
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
113
diff
changeset
|
384 uint32_t frames; |
a4d0b6c82b29
using Orthanc::DicomMap instead of OrthancPlugins::DicomDatasetReader
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
113
diff
changeset
|
385 if (dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES) && |
32 | 386 frames != 1) |
0 | 387 { |
388 LOG(ERROR) << "One instance in this series has more than 1 frame"; | |
389 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
390 } | |
391 | |
392 return dataset.release(); | |
393 } | |
394 | |
395 | |
396 void OrthancSeriesLoader::CheckFrame(const Orthanc::ImageAccessor& frame) const | |
397 { | |
398 if (frame.GetFormat() != format_ || | |
399 frame.GetWidth() != width_ || | |
400 frame.GetHeight() != height_) | |
401 { | |
402 LOG(ERROR) << "The parameters of this series vary accross its slices"; | |
403 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
404 } | |
405 } | |
406 | |
407 | |
408 Orthanc::ImageAccessor* OrthancSeriesLoader::DownloadFrame(size_t index) | |
409 { | |
410 const Slice& slice = slices_->GetSlice(index); | |
411 | |
412 std::auto_ptr<Orthanc::ImageAccessor> frame | |
413 (MessagingToolbox::DecodeFrame(orthanc_, slice.GetInstanceId(), 0, format_)); | |
414 | |
415 if (frame.get() != NULL) | |
416 { | |
417 CheckFrame(*frame); | |
418 } | |
419 | |
420 return frame.release(); | |
421 } | |
422 | |
423 | |
424 Orthanc::ImageAccessor* OrthancSeriesLoader::DownloadJpegFrame(size_t index, | |
425 unsigned int quality) | |
426 { | |
427 const Slice& slice = slices_->GetSlice(index); | |
428 | |
429 std::auto_ptr<Orthanc::ImageAccessor> frame | |
430 (MessagingToolbox::DecodeJpegFrame(orthanc_, slice.GetInstanceId(), 0, quality, format_)); | |
431 | |
432 if (frame.get() != NULL) | |
433 { | |
434 CheckFrame(*frame); | |
435 } | |
436 | |
437 return frame.release(); | |
438 } | |
439 | |
440 | |
441 bool OrthancSeriesLoader::IsJpegAvailable() | |
442 { | |
443 return MessagingToolbox::HasWebViewerInstalled(orthanc_); | |
444 } | |
445 } |