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