comparison OrthancCppClient/Series.cpp @ 479:0cd977e94479

initial commit of the c++ client
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 16 Jul 2013 09:08:09 +0200
parents
children f3d4193c571a
comparison
equal deleted inserted replaced
478:888f8a778e70 479:0cd977e94479
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
4 * 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 "Series.h"
34
35 #include "OrthancConnection.h"
36 #include "../Core/OrthancException.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://www.itk.org/pipermail/insight-users/2003-September/004762.html
57 **/
58
59 std::vector<float> cosines;
60 someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient");
61
62 if (cosines.size() != 6)
63 {
64 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
65 }
66
67 normal_[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4];
68 normal_[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5];
69 normal_[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3];
70 }
71
72
73 /**
74 * Compute the distance of some slice along the slice normal.
75 **/
76 float ComputeSliceLocation(Instance& instance) const
77 {
78 std::vector<float> ipp;
79 instance.SplitVectorOfFloats(ipp, "ImagePositionPatient");
80 if (ipp.size() != 3)
81 {
82 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
83 }
84
85 float dist = 0;
86
87 for (int i = 0; i < 3; i++)
88 {
89 dist += normal_[i] * ipp[i];
90 }
91
92 return dist;
93 }
94 };
95
96 class ImageDownloadCommand : public Orthanc::ICommand
97 {
98 private:
99 Orthanc::PixelFormat format_;
100 Orthanc::ImageExtractionMode mode_;
101 Instance& instance_;
102 void* target_;
103 size_t lineStride_;
104
105 public:
106 ImageDownloadCommand(Instance& instance,
107 Orthanc::PixelFormat format,
108 Orthanc::ImageExtractionMode mode,
109 void* target,
110 size_t lineStride) :
111 format_(format),
112 mode_(mode),
113 instance_(instance),
114 target_(target),
115 lineStride_(lineStride)
116 {
117 instance_.SetImageExtractionMode(mode);
118 }
119
120 virtual bool Execute()
121 {
122 using namespace Orthanc;
123
124 unsigned int width = instance_.GetHeight();
125
126 for (unsigned int y = 0; y < instance_.GetHeight(); y++)
127 {
128 uint8_t* p = reinterpret_cast<uint8_t*>(target_) + y * lineStride_;
129
130 if (instance_.GetPixelFormat() == format_)
131 {
132 memcpy(p, instance_.GetBuffer(y), 2 * instance_.GetWidth());
133 }
134 else if (instance_.GetPixelFormat() == PixelFormat_Grayscale8 &&
135 format_ == PixelFormat_RGB24)
136 {
137 const uint8_t* s = reinterpret_cast<const uint8_t*>(instance_.GetBuffer(y));
138 for (unsigned int x = 0; x < width; x++, s++, p += 3)
139 {
140 p[0] = *s;
141 p[1] = *s;
142 p[2] = *s;
143 }
144 }
145 else
146 {
147 throw OrthancException(ErrorCode_NotImplemented);
148 }
149 }
150
151 // Do not keep the image in memory, as we are loading 3D images
152 instance_.DiscardImage();
153
154 return true;
155 }
156 };
157 }
158
159
160 void Series::Check3DImage()
161 {
162 if (!Is3DImage())
163 {
164 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
165 }
166 }
167
168 bool Series::Is3DImageInternal()
169 {
170 try
171 {
172 if (GetInstanceCount() == 0)
173 {
174 return true;
175 }
176
177 for (unsigned int i = 0; i < GetInstanceCount(); i++)
178 {
179 if (GetInstance(0).GetTagAsString("Columns") != GetInstance(i).GetTagAsString("Columns") ||
180 GetInstance(0).GetTagAsString("Rows") != GetInstance(i).GetTagAsString("Rows") ||
181 GetInstance(0).GetTagAsString("ImageOrientationPatient") != GetInstance(i).GetTagAsString("ImageOrientationPatient") ||
182 GetInstance(0).GetTagAsString("SliceThickness") != GetInstance(i).GetTagAsString("SliceThickness") ||
183 GetInstance(0).GetTagAsString("PixelSpacing") != GetInstance(i).GetTagAsString("PixelSpacing"))
184 {
185 return false;
186 }
187 }
188
189 SliceLocator locator(GetInstance(0));
190 std::set<float> l;
191 for (unsigned int i = 0; i < GetInstanceCount(); i++)
192 {
193 l.insert(locator.ComputeSliceLocation(GetInstance(i)));
194 }
195
196 return l.size() == GetInstanceCount();
197 }
198 catch (Orthanc::OrthancException)
199 {
200 return false;
201 }
202 }
203
204 void Series::ReadSeries()
205 {
206 Orthanc::HttpClient client(connection_.GetHttpClient());
207
208 client.SetUrl(connection_.GetOrthancUrl() + "/series/" + id_);
209 Json::Value v;
210 if (!client.Apply(series_))
211 {
212 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
213 }
214 }
215
216 Orthanc::IDynamicObject* Series::GetFillerItem(size_t index)
217 {
218 return new Instance(connection_, series_["Instances"][index].asString());
219 }
220
221 Series::Series(const OrthancConnection& connection,
222 const std::string& id) :
223 connection_(connection),
224 id_(id),
225 instances_(*this)
226 {
227 ReadSeries();
228 status_ = Status3DImage_NotTested;
229
230 instances_.SetThreadCount(connection.GetThreadCount());
231 }
232
233
234 bool Series::Is3DImage()
235 {
236 if (status_ == Status3DImage_NotTested)
237 {
238 status_ = Is3DImageInternal() ? Status3DImage_True : Status3DImage_False;
239 }
240
241 return status_ == Status3DImage_True;
242 }
243
244 unsigned int Series::GetInstanceCount()
245 {
246 return instances_.GetSize();
247 }
248
249 Instance& Series::GetInstance(unsigned int index)
250 {
251 return dynamic_cast<Instance&>(instances_.GetItem(index));
252 }
253
254 std::string Series::GetUrl() const
255 {
256 return connection_.GetOrthancUrl() + "/series/" + id_;
257 }
258
259 unsigned int Series::GetWidth()
260 {
261 Check3DImage();
262
263 if (GetInstanceCount() == 0)
264 return 0;
265 else
266 return GetInstance(0).GetTagAsInt("Columns");
267 }
268
269 unsigned int Series::GetHeight()
270 {
271 Check3DImage();
272
273 if (GetInstanceCount() == 0)
274 return 0;
275 else
276 return GetInstance(0).GetTagAsInt("Rows");
277 }
278
279 void Series::GetVoxelSize(float& sizeX, float& sizeY, float& sizeZ)
280 {
281 Check3DImage();
282
283 if (GetInstanceCount() == 0)
284 {
285 sizeX = 0;
286 sizeY = 0;
287 sizeZ = 0;
288 }
289 else
290 {
291 try
292 {
293 std::string s = GetInstance(0).GetTagAsString("PixelSpacing");
294 size_t pos = s.find('\\');
295 assert(pos != std::string::npos);
296 std::string sy = s.substr(0, pos);
297 std::string sx = s.substr(pos + 1);
298
299 sizeX = boost::lexical_cast<float>(sx);
300 sizeY = boost::lexical_cast<float>(sy);
301 sizeZ = GetInstance(0).GetTagAsFloat("SliceThickness");
302 }
303 catch (boost::bad_lexical_cast)
304 {
305 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
306 }
307 }
308 }
309
310
311 std::string Series::GetMainDicomTag(const char* tag, const char* defaultValue) const
312 {
313 if (series_["MainDicomTags"].isMember(tag))
314 {
315 return series_["MainDicomTags"][tag].asString();
316 }
317 else
318 {
319 return defaultValue;
320 }
321 }
322
323
324
325 void Series::Load3DImage(void* target,
326 Orthanc::PixelFormat format,
327 size_t lineStride,
328 size_t stackStride,
329 Orthanc::ThreadedCommandProcessor::IListener* listener)
330 {
331 using namespace Orthanc;
332
333 // Choose the extraction mode, depending on the format of the
334 // target image.
335
336 uint8_t bytesPerPixel;
337 ImageExtractionMode mode;
338
339 switch (format)
340 {
341 case PixelFormat_RGB24:
342 bytesPerPixel = 3;
343 mode = ImageExtractionMode_Preview;
344 break;
345
346 case PixelFormat_Grayscale8:
347 bytesPerPixel = 1;
348 mode = ImageExtractionMode_UInt8; // Preview ???
349 break;
350
351 case PixelFormat_Grayscale16:
352 bytesPerPixel = 2;
353 mode = ImageExtractionMode_UInt16;
354 break;
355
356 case PixelFormat_SignedGrayscale16:
357 bytesPerPixel = 2;
358 mode = ImageExtractionMode_UInt16;
359 format = PixelFormat_Grayscale16;
360 break;
361
362 default:
363 throw OrthancException(ErrorCode_NotImplemented);
364 }
365
366
367 // Check that the target image is properly sized
368 unsigned int sx = GetWidth();
369 unsigned int sy = GetHeight();
370
371 if (lineStride < sx * bytesPerPixel ||
372 stackStride < sx * sy * bytesPerPixel)
373 {
374 throw OrthancException(ErrorCode_BadRequest);
375 }
376
377 if (sx == 0 || sy == 0 || GetInstanceCount() == 0)
378 {
379 // Empty image, nothing to do
380 if (listener)
381 listener->SignalSuccess(0);
382 return;
383 }
384
385
386 /**
387 * Order the stacks according to their distance along the slice
388 * normal (using the "Image Position Patient" tag). This works
389 * even if the "SliceLocation" tag is absent.
390 **/
391 SliceLocator locator(GetInstance(0));
392
393 typedef std::map<float, Instance*> Instances;
394 Instances instances;
395 for (unsigned int i = 0; i < GetInstanceCount(); i++)
396 {
397 float dist = locator.ComputeSliceLocation(GetInstance(i));
398 instances[dist] = &GetInstance(i);
399 }
400
401 if (instances.size() != GetInstanceCount())
402 {
403 // Several instances have the same Z coordinate
404 throw OrthancException(ErrorCode_NotImplemented);
405 }
406
407
408 // Submit the download of each stack as a set of commands
409 ThreadedCommandProcessor processor(connection_.GetThreadCount());
410
411 if (listener != NULL)
412 {
413 processor.SetListener(*listener);
414 }
415
416 uint8_t* stackTarget = reinterpret_cast<uint8_t*>(target);
417 for (Instances::iterator it = instances.begin(); it != instances.end(); it++)
418 {
419 processor.Post(new ImageDownloadCommand(*it->second, format, mode, stackTarget, lineStride));
420 stackTarget += stackStride;
421 }
422
423
424 // Wait for all the stacks to be downloaded
425 if (!processor.Join())
426 {
427 throw OrthancException(ErrorCode_NetworkProtocol);
428 }
429 }
430
431 }