comparison OrthancCppClient/Series.cpp @ 0:ebc1e38ef615

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 01 Jun 2015 09:47:32 +0200
parents
children d5027f9f676a
comparison
equal deleted inserted replaced
-1:000000000000 0:ebc1e38ef615
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2015 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 "../Core/PrecompiledHeaders.h"
34 #include "Series.h"
35
36 #include "OrthancConnection.h"
37
38 #include <set>
39 #include <boost/lexical_cast.hpp>
40
41 namespace OrthancClient
42 {
43 namespace
44 {
45 class SliceLocator
46 {
47 private:
48 float normal_[3];
49
50 public:
51 SliceLocator(Instance& someSlice)
52 {
53 /**
54 * Compute the slice normal from Image Orientation Patient.
55 * http://nipy.sourceforge.net/nibabel/dicom/dicom_orientation.html#dicom-z-from-slice
56 * http://dicomiseasy.blogspot.be/2013/06/getting-oriented-using-image-plane.html
57 * http://www.itk.org/pipermail/insight-users/2003-September/004762.html
58 **/
59
60 std::vector<float> cosines;
61 someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient"); // 0020-0037
62
63 if (cosines.size() != 6)
64 {
65 throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
66 }
67
68 normal_[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4];
69 normal_[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5];
70 normal_[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3];
71 }
72
73
74 /**
75 * Compute the distance of some slice along the slice normal.
76 **/
77 float ComputeSliceLocation(Instance& instance) const
78 {
79 std::vector<float> ipp;
80 instance.SplitVectorOfFloats(ipp, "ImagePositionPatient"); // 0020-0032
81 if (ipp.size() != 3)
82 {
83 throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
84 }
85
86 float dist = 0;
87
88 for (int i = 0; i < 3; i++)
89 {
90 dist += normal_[i] * ipp[i];
91 }
92
93 return dist;
94 }
95 };
96
97 class ImageDownloadCommand : public Orthanc::ICommand
98 {
99 private:
100 Orthanc::PixelFormat format_;
101 Orthanc::ImageExtractionMode mode_;
102 Instance& instance_;
103 void* target_;
104 size_t lineStride_;
105
106 public:
107 ImageDownloadCommand(Instance& instance,
108 Orthanc::PixelFormat format,
109 Orthanc::ImageExtractionMode mode,
110 void* target,
111 size_t lineStride) :
112 format_(format),
113 mode_(mode),
114 instance_(instance),
115 target_(target),
116 lineStride_(lineStride)
117 {
118 instance_.SetImageExtractionMode(mode);
119 }
120
121 virtual bool Execute()
122 {
123 using namespace Orthanc;
124
125 unsigned int width = instance_.GetHeight();
126
127 for (unsigned int y = 0; y < instance_.GetHeight(); y++)
128 {
129 uint8_t* p = reinterpret_cast<uint8_t*>(target_) + y * lineStride_;
130
131 if (instance_.GetPixelFormat() == format_)
132 {
133 memcpy(p, instance_.GetBuffer(y), GetBytesPerPixel(instance_.GetPixelFormat()) * instance_.GetWidth());
134 }
135 else if (instance_.GetPixelFormat() == PixelFormat_Grayscale8 &&
136 format_ == PixelFormat_RGB24)
137 {
138 const uint8_t* s = reinterpret_cast<const uint8_t*>(instance_.GetBuffer(y));
139 for (unsigned int x = 0; x < width; x++, s++, p += 3)
140 {
141 p[0] = *s;
142 p[1] = *s;
143 p[2] = *s;
144 }
145 }
146 else
147 {
148 throw OrthancClientException(ErrorCode_NotImplemented);
149 }
150 }
151
152 // Do not keep the image in memory, as we are loading 3D images
153 instance_.DiscardImage();
154
155 return true;
156 }
157 };
158
159
160 class ProgressToFloatListener : public Orthanc::ThreadedCommandProcessor::IListener
161 {
162 private:
163 float* target_;
164
165 public:
166 ProgressToFloatListener(float* target) : target_(target)
167 {
168 }
169
170 virtual void SignalProgress(unsigned int current,
171 unsigned int total)
172 {
173 if (total == 0)
174 {
175 *target_ = 0;
176 }
177 else
178 {
179 *target_ = static_cast<float>(current) / static_cast<float>(total);
180 }
181 }
182
183 virtual void SignalSuccess(unsigned int total)
184 {
185 *target_ = 1;
186 }
187
188 virtual void SignalFailure()
189 {
190 *target_ = 0;
191 }
192
193 virtual void SignalCancel()
194 {
195 *target_ = 0;
196 }
197 };
198
199 }
200
201
202 void Series::Check3DImage()
203 {
204 if (!Is3DImage())
205 {
206 throw OrthancClientException(Orthanc::ErrorCode_NotImplemented);
207 }
208 }
209
210 bool Series::Is3DImageInternal()
211 {
212 try
213 {
214 if (GetInstanceCount() == 0)
215 {
216 // Empty image, use some default value (should never happen)
217 voxelSizeX_ = 1;
218 voxelSizeY_ = 1;
219 voxelSizeZ_ = 1;
220 sliceThickness_ = 1;
221
222 return true;
223 }
224
225 // Choose a reference slice
226 Instance& reference = GetInstance(0);
227
228 // Check that all the child instances share the same 3D parameters
229 for (unsigned int i = 0; i < GetInstanceCount(); i++)
230 {
231 Instance& i2 = GetInstance(i);
232
233 if (std::string(reference.GetTagAsString("Columns")) != std::string(i2.GetTagAsString("Columns")) ||
234 std::string(reference.GetTagAsString("Rows")) != std::string(i2.GetTagAsString("Rows")) ||
235 std::string(reference.GetTagAsString("ImageOrientationPatient")) != std::string(i2.GetTagAsString("ImageOrientationPatient")) ||
236 std::string(reference.GetTagAsString("SliceThickness")) != std::string(i2.GetTagAsString("SliceThickness")) ||
237 std::string(reference.GetTagAsString("PixelSpacing")) != std::string(i2.GetTagAsString("PixelSpacing")))
238 {
239 return false;
240 }
241 }
242
243
244 // Extract X/Y voxel size and slice thickness
245 std::string s = GetInstance(0).GetTagAsString("PixelSpacing"); // 0028-0030
246 size_t pos = s.find('\\');
247 assert(pos != std::string::npos);
248 std::string sy = s.substr(0, pos);
249 std::string sx = s.substr(pos + 1);
250
251 try
252 {
253 voxelSizeX_ = boost::lexical_cast<float>(sx);
254 voxelSizeY_ = boost::lexical_cast<float>(sy);
255 }
256 catch (boost::bad_lexical_cast)
257 {
258 throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
259 }
260
261 sliceThickness_ = GetInstance(0).GetTagAsFloat("SliceThickness"); // 0018-0050
262
263
264 // Compute the location of each slice to extract the voxel size along Z
265 voxelSizeZ_ = std::numeric_limits<float>::infinity();
266
267 SliceLocator locator(reference);
268 float referenceSliceLocation = locator.ComputeSliceLocation(reference);
269
270 std::set<float> l;
271 for (unsigned int i = 0; i < GetInstanceCount(); i++)
272 {
273 float location = locator.ComputeSliceLocation(GetInstance(i));
274 float distanceToReferenceSlice = fabs(location - referenceSliceLocation);
275
276 l.insert(location);
277
278 if (distanceToReferenceSlice > std::numeric_limits<float>::epsilon() &&
279 distanceToReferenceSlice < voxelSizeZ_)
280 {
281 voxelSizeZ_ = distanceToReferenceSlice;
282 }
283 }
284
285
286 // Make sure that 2 slices do not share the same Z location
287 return l.size() == GetInstanceCount();
288 }
289 catch (OrthancClientException)
290 {
291 return false;
292 }
293 }
294
295 void Series::ReadSeries()
296 {
297 Orthanc::HttpClient client(connection_.GetHttpClient());
298
299 client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/series/" + id_);
300 Json::Value v;
301 if (!client.Apply(series_))
302 {
303 throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
304 }
305 }
306
307 Orthanc::IDynamicObject* Series::GetFillerItem(size_t index)
308 {
309 Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index);
310 std::string id = series_["Instances"][tmp].asString();
311 return new Instance(connection_, id.c_str());
312 }
313
314 Series::Series(const OrthancConnection& connection,
315 const char* id) :
316 connection_(connection),
317 id_(id),
318 instances_(*this)
319 {
320 ReadSeries();
321 status_ = Status3DImage_NotTested;
322 url_ = std::string(connection_.GetOrthancUrl()) + "/series/" + id_;
323
324 voxelSizeX_ = 0;
325 voxelSizeY_ = 0;
326 voxelSizeZ_ = 0;
327 sliceThickness_ = 0;
328
329 instances_.SetThreadCount(connection.GetThreadCount());
330 }
331
332
333 bool Series::Is3DImage()
334 {
335 if (status_ == Status3DImage_NotTested)
336 {
337 status_ = Is3DImageInternal() ? Status3DImage_True : Status3DImage_False;
338 }
339
340 return status_ == Status3DImage_True;
341 }
342
343 unsigned int Series::GetInstanceCount()
344 {
345 return instances_.GetSize();
346 }
347
348 Instance& Series::GetInstance(unsigned int index)
349 {
350 return dynamic_cast<Instance&>(instances_.GetItem(index));
351 }
352
353 unsigned int Series::GetWidth()
354 {
355 Check3DImage();
356
357 if (GetInstanceCount() == 0)
358 return 0;
359 else
360 return GetInstance(0).GetTagAsInt("Columns");
361 }
362
363 unsigned int Series::GetHeight()
364 {
365 Check3DImage();
366
367 if (GetInstanceCount() == 0)
368 return 0;
369 else
370 return GetInstance(0).GetTagAsInt("Rows");
371 }
372
373 const char* Series::GetMainDicomTag(const char* tag, const char* defaultValue) const
374 {
375 if (series_["MainDicomTags"].isMember(tag))
376 {
377 return series_["MainDicomTags"][tag].asCString();
378 }
379 else
380 {
381 return defaultValue;
382 }
383 }
384
385
386
387 void Series::Load3DImageInternal(void* target,
388 Orthanc::PixelFormat format,
389 size_t lineStride,
390 size_t stackStride,
391 Orthanc::ThreadedCommandProcessor::IListener* listener)
392 {
393 using namespace Orthanc;
394
395 // Choose the extraction mode, depending on the format of the
396 // target image.
397
398 uint8_t bytesPerPixel;
399 ImageExtractionMode mode;
400
401 switch (format)
402 {
403 case PixelFormat_RGB24:
404 bytesPerPixel = 3;
405 mode = ImageExtractionMode_Preview;
406 break;
407
408 case PixelFormat_Grayscale8:
409 bytesPerPixel = 1;
410 mode = ImageExtractionMode_UInt8; // Preview ???
411 break;
412
413 case PixelFormat_Grayscale16:
414 bytesPerPixel = 2;
415 mode = ImageExtractionMode_UInt16;
416 break;
417
418 case PixelFormat_SignedGrayscale16:
419 bytesPerPixel = 2;
420 mode = ImageExtractionMode_UInt16;
421 format = PixelFormat_Grayscale16;
422 break;
423
424 default:
425 throw OrthancClientException(ErrorCode_NotImplemented);
426 }
427
428
429 // Check that the target image is properly sized
430 unsigned int sx = GetWidth();
431 unsigned int sy = GetHeight();
432
433 if (lineStride < sx * bytesPerPixel ||
434 stackStride < sx * sy * bytesPerPixel)
435 {
436 throw OrthancClientException(ErrorCode_BadRequest);
437 }
438
439 if (sx == 0 || sy == 0 || GetInstanceCount() == 0)
440 {
441 // Empty image, nothing to do
442 if (listener)
443 listener->SignalSuccess(0);
444 return;
445 }
446
447
448 /**
449 * Order the stacks according to their distance along the slice
450 * normal (using the "Image Position Patient" tag). This works
451 * even if the "SliceLocation" tag is absent.
452 **/
453 SliceLocator locator(GetInstance(0));
454
455 typedef std::map<float, Instance*> Instances;
456 Instances instances;
457 for (unsigned int i = 0; i < GetInstanceCount(); i++)
458 {
459 float dist = locator.ComputeSliceLocation(GetInstance(i));
460 instances[dist] = &GetInstance(i);
461 }
462
463 if (instances.size() != GetInstanceCount())
464 {
465 // Several instances have the same Z coordinate
466 throw OrthancClientException(ErrorCode_NotImplemented);
467 }
468
469
470 // Submit the download of each stack as a set of commands
471 ThreadedCommandProcessor processor(connection_.GetThreadCount());
472
473 if (listener != NULL)
474 {
475 processor.SetListener(*listener);
476 }
477
478 uint8_t* stackTarget = reinterpret_cast<uint8_t*>(target);
479 for (Instances::iterator it = instances.begin(); it != instances.end(); ++it)
480 {
481 processor.Post(new ImageDownloadCommand(*it->second, format, mode, stackTarget, lineStride));
482 stackTarget += stackStride;
483 }
484
485
486 // Wait for all the stacks to be downloaded
487 if (!processor.Join())
488 {
489 throw OrthancClientException(ErrorCode_NetworkProtocol);
490 }
491 }
492
493 float Series::GetVoxelSizeX()
494 {
495 Check3DImage(); // Is3DImageInternal() will compute the voxel sizes
496 return voxelSizeX_;
497 }
498
499 float Series::GetVoxelSizeY()
500 {
501 Check3DImage(); // Is3DImageInternal() will compute the voxel sizes
502 return voxelSizeY_;
503 }
504
505 float Series::GetVoxelSizeZ()
506 {
507 Check3DImage(); // Is3DImageInternal() will compute the voxel sizes
508 return voxelSizeZ_;
509 }
510
511 float Series::GetSliceThickness()
512 {
513 Check3DImage(); // Is3DImageInternal() will compute the voxel sizes
514 return sliceThickness_;
515 }
516
517 void Series::Load3DImage(void* target,
518 Orthanc::PixelFormat format,
519 int64_t lineStride,
520 int64_t stackStride,
521 float* progress)
522 {
523 ProgressToFloatListener listener(progress);
524 Load3DImageInternal(target, format, static_cast<size_t>(lineStride),
525 static_cast<size_t>(stackStride), &listener);
526 }
527 }