Mercurial > hg > orthanc
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 } |