comparison Framework/Toolbox/OrthancSeriesLoader.cpp @ 0:351ab0da0150

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