Mercurial > hg > orthanc-stone
annotate Framework/Radiography/RadiographyScene.cpp @ 431:26b90b110719 am-vsol-upgrade
added DelayedCallExecutor to avoid using sleep() in C++ that consumes 100% CPU once executed in WASM
author | am@osimis.io |
---|---|
date | Thu, 29 Nov 2018 19:25:15 +0100 |
parents | b85f635f1eb5 |
children | 4eb96c6b4e96 |
rev | line source |
---|---|
408 | 1 /** |
2 * Stone of Orthanc | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2018 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
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. | |
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 | |
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 | |
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 **/ | |
20 | |
21 | |
22 #include "RadiographyScene.h" | |
23 | |
430 | 24 #include "RadiographyAlphaLayer.h" |
25 #include "RadiographyDicomLayer.h" | |
26 #include "RadiographyTextLayer.h" | |
408 | 27 #include "../Toolbox/DicomFrameConverter.h" |
28 | |
29 #include <Core/Images/Image.h> | |
30 #include <Core/Images/ImageProcessing.h> | |
31 #include <Core/Images/PamReader.h> | |
32 #include <Core/Images/PamWriter.h> | |
33 #include <Core/Images/PngWriter.h> | |
34 #include <Core/OrthancException.h> | |
35 #include <Core/Toolbox.h> | |
36 #include <Plugins/Samples/Common/DicomDatasetReader.h> | |
37 #include <Plugins/Samples/Common/FullOrthancDataset.h> | |
38 | |
412 | 39 #include <boost/math/special_functions/round.hpp> |
40 | |
408 | 41 |
42 namespace OrthancStone | |
43 { | |
44 RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene, | |
45 size_t index) : | |
46 scene_(scene), | |
47 index_(index) | |
48 { | |
49 Layers::iterator layer = scene.layers_.find(index); | |
50 if (layer == scene.layers_.end()) | |
51 { | |
52 layer_ = NULL; | |
53 } | |
54 else | |
55 { | |
56 assert(layer->second != NULL); | |
57 layer_ = layer->second; | |
58 } | |
59 } | |
60 | |
426 | 61 |
408 | 62 RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene, |
63 double x, | |
64 double y) : | |
65 scene_(scene), | |
66 index_(0) // Dummy initialization | |
67 { | |
68 if (scene.LookupLayer(index_, x, y)) | |
69 { | |
70 Layers::iterator layer = scene.layers_.find(index_); | |
426 | 71 |
408 | 72 if (layer == scene.layers_.end()) |
73 { | |
74 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
75 } | |
76 else | |
77 { | |
78 assert(layer->second != NULL); | |
79 layer_ = layer->second; | |
80 } | |
81 } | |
82 else | |
83 { | |
84 layer_ = NULL; | |
85 } | |
86 } | |
87 | |
88 | |
89 RadiographyScene& RadiographyScene::LayerAccessor::GetScene() const | |
90 { | |
91 if (IsValid()) | |
92 { | |
93 return scene_; | |
94 } | |
95 else | |
96 { | |
97 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
98 } | |
99 } | |
100 | |
101 | |
102 size_t RadiographyScene::LayerAccessor::GetIndex() const | |
103 { | |
104 if (IsValid()) | |
105 { | |
106 return index_; | |
107 } | |
108 else | |
109 { | |
110 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
111 } | |
112 } | |
113 | |
114 | |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
115 RadiographyLayer& RadiographyScene::LayerAccessor::GetLayer() const |
408 | 116 { |
117 if (IsValid()) | |
118 { | |
119 return *layer_; | |
120 } | |
121 else | |
122 { | |
123 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
124 } | |
426 | 125 } |
408 | 126 |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
127 RadiographyLayer& RadiographyScene::RegisterLayer(RadiographyLayer* layer) |
408 | 128 { |
129 if (layer == NULL) | |
130 { | |
131 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
132 } | |
133 | |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
134 std::auto_ptr<RadiographyLayer> raii(layer); |
426 | 135 |
428
751fb354149e
ability to change the scene of the RadiographyWidget
am@osimis.io
parents:
426
diff
changeset
|
136 LOG(INFO) << "Registering layer: " << countLayers_; |
751fb354149e
ability to change the scene of the RadiographyWidget
am@osimis.io
parents:
426
diff
changeset
|
137 |
408 | 138 size_t index = countLayers_++; |
139 raii->SetIndex(index); | |
140 layers_[index] = raii.release(); | |
141 | |
430 | 142 EmitMessage(GeometryChangedMessage(*this, *layer)); |
143 EmitMessage(ContentChangedMessage(*this, *layer)); | |
408 | 144 |
145 return *layer; | |
146 } | |
426 | 147 |
408 | 148 |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
149 RadiographyScene::RadiographyScene(MessageBroker& broker) : |
408 | 150 IObserver(broker), |
151 IObservable(broker), | |
152 countLayers_(0), | |
153 hasWindowing_(false), | |
154 windowingCenter_(0), // Dummy initialization | |
155 windowingWidth_(0) // Dummy initialization | |
156 { | |
157 } | |
158 | |
159 | |
160 RadiographyScene::~RadiographyScene() | |
161 { | |
162 for (Layers::iterator it = layers_.begin(); it != layers_.end(); it++) | |
163 { | |
164 assert(it->second != NULL); | |
165 delete it->second; | |
166 } | |
167 } | |
168 | |
430 | 169 void RadiographyScene::GetLayersIndexes(std::vector<size_t>& output) const |
170 { | |
171 for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++) | |
172 { | |
173 output.push_back(it->first); | |
174 } | |
175 } | |
176 | |
425 | 177 void RadiographyScene::RemoveLayer(size_t layerIndex) |
178 { | |
428
751fb354149e
ability to change the scene of the RadiographyWidget
am@osimis.io
parents:
426
diff
changeset
|
179 LOG(INFO) << "Removing layer: " << layerIndex; |
751fb354149e
ability to change the scene of the RadiographyWidget
am@osimis.io
parents:
426
diff
changeset
|
180 |
425 | 181 if (layerIndex > countLayers_) |
182 { | |
183 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
184 } | |
185 delete layers_[layerIndex]; | |
186 layers_.erase(layerIndex); | |
187 countLayers_--; | |
428
751fb354149e
ability to change the scene of the RadiographyWidget
am@osimis.io
parents:
426
diff
changeset
|
188 LOG(INFO) << "Removing layer, there are now : " << countLayers_ << " layers"; |
425 | 189 } |
190 | |
430 | 191 const RadiographyLayer& RadiographyScene::GetLayer(size_t layerIndex) const |
425 | 192 { |
193 if (layerIndex > countLayers_) | |
194 { | |
195 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
196 } | |
430 | 197 |
198 return *(layers_.at(layerIndex)); | |
425 | 199 } |
408 | 200 |
201 bool RadiographyScene::GetWindowing(float& center, | |
202 float& width) const | |
203 { | |
204 if (hasWindowing_) | |
205 { | |
206 center = windowingCenter_; | |
207 width = windowingWidth_; | |
208 return true; | |
209 } | |
210 else | |
211 { | |
212 return false; | |
213 } | |
214 } | |
215 | |
216 | |
217 void RadiographyScene::GetWindowingWithDefault(float& center, | |
218 float& width) const | |
219 { | |
220 if (!GetWindowing(center, width)) | |
221 { | |
222 center = 128; | |
223 width = 256; | |
224 } | |
225 } | |
226 | |
227 | |
228 void RadiographyScene::SetWindowing(float center, | |
229 float width) | |
230 { | |
231 hasWindowing_ = true; | |
232 windowingCenter_ = center; | |
233 windowingWidth_ = width; | |
234 } | |
235 | |
236 | |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
237 RadiographyLayer& RadiographyScene::LoadText(const Orthanc::Font& font, |
430 | 238 const std::string& utf8, |
239 RadiographyLayer::Geometry* geometry) | |
408 | 240 { |
430 | 241 std::auto_ptr<RadiographyTextLayer> alpha(new RadiographyTextLayer(*this)); |
408 | 242 alpha->LoadText(font, utf8); |
430 | 243 if (geometry != NULL) |
244 { | |
245 alpha->SetGeometry(*geometry); | |
246 } | |
408 | 247 |
248 return RegisterLayer(alpha.release()); | |
249 } | |
250 | |
426 | 251 |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
252 RadiographyLayer& RadiographyScene::LoadTestBlock(unsigned int width, |
430 | 253 unsigned int height, |
254 RadiographyLayer::Geometry* geometry) | |
408 | 255 { |
256 std::auto_ptr<Orthanc::Image> block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false)); | |
257 | |
258 for (unsigned int padding = 0; | |
259 (width > 2 * padding) && (height > 2 * padding); | |
260 padding++) | |
261 { | |
262 uint8_t color; | |
263 if (255 > 10 * padding) | |
264 { | |
265 color = 255 - 10 * padding; | |
266 } | |
267 else | |
268 { | |
269 color = 0; | |
270 } | |
271 | |
272 Orthanc::ImageAccessor region; | |
273 block->GetRegion(region, padding, padding, width - 2 * padding, height - 2 * padding); | |
274 Orthanc::ImageProcessing::Set(region, color); | |
275 } | |
276 | |
430 | 277 return LoadAlphaBitmap(block.release(), geometry); |
278 } | |
279 | |
280 RadiographyLayer& RadiographyScene::LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap, RadiographyLayer::Geometry *geometry) | |
281 { | |
282 std::auto_ptr<RadiographyAlphaLayer> alpha(new RadiographyAlphaLayer(*this)); | |
283 alpha->SetAlpha(bitmap); | |
284 if (geometry != NULL) | |
285 { | |
286 alpha->SetGeometry(*geometry); | |
287 } | |
408 | 288 |
289 return RegisterLayer(alpha.release()); | |
290 } | |
291 | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
292 RadiographyLayer& RadiographyScene::LoadDicomFrame(OrthancApiClient& orthanc, |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
293 const std::string& instance, |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
294 unsigned int frame, |
430 | 295 bool httpCompression, |
296 RadiographyLayer::Geometry* geometry) | |
408 | 297 { |
430 | 298 RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer)); |
299 layer.SetInstance(instance, frame); | |
300 | |
301 if (geometry != NULL) | |
302 { | |
303 layer.SetGeometry(*geometry); | |
304 } | |
408 | 305 |
306 { | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
307 IWebService::HttpHeaders headers; |
408 | 308 std::string uri = "/instances/" + instance + "/tags"; |
426 | 309 |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
310 orthanc.GetBinaryAsync( |
426 | 311 uri, headers, |
312 new Callable<RadiographyScene, OrthancApiClient::BinaryResponseReadyMessage> | |
313 (*this, &RadiographyScene::OnTagsReceived), NULL, | |
314 new Orthanc::SingleValueObject<size_t>(layer.GetIndex())); | |
408 | 315 } |
316 | |
317 { | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
318 IWebService::HttpHeaders headers; |
408 | 319 headers["Accept"] = "image/x-portable-arbitrarymap"; |
320 | |
321 if (httpCompression) | |
322 { | |
323 headers["Accept-Encoding"] = "gzip"; | |
324 } | |
426 | 325 |
408 | 326 std::string uri = ("/instances/" + instance + "/frames/" + |
327 boost::lexical_cast<std::string>(frame) + "/image-uint16"); | |
426 | 328 |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
329 orthanc.GetBinaryAsync( |
426 | 330 uri, headers, |
331 new Callable<RadiographyScene, OrthancApiClient::BinaryResponseReadyMessage> | |
332 (*this, &RadiographyScene::OnFrameReceived), NULL, | |
333 new Orthanc::SingleValueObject<size_t>(layer.GetIndex())); | |
408 | 334 } |
335 | |
336 return layer; | |
337 } | |
338 | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
339 |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
340 RadiographyLayer& RadiographyScene::LoadDicomWebFrame(IWebService& web) |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
341 { |
430 | 342 RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer); |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
343 |
426 | 344 |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
345 return layer; |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
346 } |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
347 |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
348 |
426 | 349 |
408 | 350 void RadiographyScene::OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) |
351 { | |
352 size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&> | |
426 | 353 (message.GetPayload()).GetValue(); |
408 | 354 |
355 LOG(INFO) << "JSON received: " << message.GetUri().c_str() | |
356 << " (" << message.GetAnswerSize() << " bytes) for layer " << index; | |
426 | 357 |
408 | 358 Layers::iterator layer = layers_.find(index); |
359 if (layer != layers_.end()) | |
360 { | |
361 assert(layer->second != NULL); | |
426 | 362 |
408 | 363 OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer(), message.GetAnswerSize()); |
430 | 364 dynamic_cast<RadiographyDicomLayer*>(layer->second)->SetDicomTags(dicom); |
408 | 365 |
366 float c, w; | |
367 if (!hasWindowing_ && | |
368 layer->second->GetDefaultWindowing(c, w)) | |
369 { | |
370 hasWindowing_ = true; | |
371 windowingCenter_ = c; | |
372 windowingWidth_ = w; | |
373 } | |
374 | |
430 | 375 EmitMessage(GeometryChangedMessage(*this, *(layer->second))); |
408 | 376 } |
377 } | |
426 | 378 |
408 | 379 |
380 void RadiographyScene::OnFrameReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) | |
381 { | |
382 size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&>(message.GetPayload()).GetValue(); | |
426 | 383 |
408 | 384 LOG(INFO) << "DICOM frame received: " << message.GetUri().c_str() |
385 << " (" << message.GetAnswerSize() << " bytes) for layer " << index; | |
426 | 386 |
408 | 387 Layers::iterator layer = layers_.find(index); |
388 if (layer != layers_.end()) | |
389 { | |
390 assert(layer->second != NULL); | |
391 | |
392 std::string content; | |
393 if (message.GetAnswerSize() > 0) | |
394 { | |
395 content.assign(reinterpret_cast<const char*>(message.GetAnswer()), message.GetAnswerSize()); | |
396 } | |
426 | 397 |
408 | 398 std::auto_ptr<Orthanc::PamReader> reader(new Orthanc::PamReader); |
399 reader->ReadFromMemory(content); | |
430 | 400 dynamic_cast<RadiographyDicomLayer*>(layer->second)->SetSourceImage(reader.release()); |
408 | 401 |
430 | 402 EmitMessage(ContentChangedMessage(*this, *(layer->second))); |
408 | 403 } |
404 } | |
405 | |
406 | |
407 Extent2D RadiographyScene::GetSceneExtent() const | |
408 { | |
409 Extent2D extent; | |
410 | |
411 for (Layers::const_iterator it = layers_.begin(); | |
412 it != layers_.end(); ++it) | |
413 { | |
414 assert(it->second != NULL); | |
415 extent.Union(it->second->GetExtent()); | |
416 } | |
417 | |
418 return extent; | |
419 } | |
426 | 420 |
408 | 421 |
422 void RadiographyScene::Render(Orthanc::ImageAccessor& buffer, | |
409 | 423 const AffineTransform2D& viewTransform, |
408 | 424 ImageInterpolation interpolation) const |
425 { | |
431
26b90b110719
added DelayedCallExecutor to avoid using sleep() in C++ that consumes 100% CPU once executed in WASM
am@osimis.io
parents:
430
diff
changeset
|
426 Orthanc::ImageProcessing::Set(buffer, 0); |
408 | 427 |
428 // Render layers in the background-to-foreground order | |
429 for (size_t index = 0; index < countLayers_; index++) | |
430 { | |
431 Layers::const_iterator it = layers_.find(index); | |
432 if (it != layers_.end()) | |
433 { | |
434 assert(it->second != NULL); | |
435 it->second->Render(buffer, viewTransform, interpolation); | |
436 } | |
437 } | |
438 } | |
439 | |
440 | |
441 bool RadiographyScene::LookupLayer(size_t& index /* out */, | |
442 double x, | |
443 double y) const | |
444 { | |
445 // Render layers in the foreground-to-background order | |
446 for (size_t i = countLayers_; i > 0; i--) | |
447 { | |
448 index = i - 1; | |
449 Layers::const_iterator it = layers_.find(index); | |
450 if (it != layers_.end()) | |
451 { | |
452 assert(it->second != NULL); | |
453 if (it->second->Contains(x, y)) | |
454 { | |
455 return true; | |
456 } | |
457 } | |
458 } | |
459 | |
460 return false; | |
461 } | |
462 | |
426 | 463 |
408 | 464 void RadiographyScene::DrawBorder(CairoContext& context, |
465 unsigned int layer, | |
466 double zoom) | |
467 { | |
468 Layers::const_iterator found = layers_.find(layer); | |
426 | 469 |
408 | 470 if (found != layers_.end()) |
471 { | |
472 context.SetSourceColor(255, 0, 0); | |
473 found->second->DrawBorders(context, zoom); | |
474 } | |
475 } | |
476 | |
477 | |
478 void RadiographyScene::GetRange(float& minValue, | |
479 float& maxValue) const | |
480 { | |
481 bool first = true; | |
426 | 482 |
408 | 483 for (Layers::const_iterator it = layers_.begin(); |
484 it != layers_.end(); it++) | |
485 { | |
486 assert(it->second != NULL); | |
487 | |
488 float a, b; | |
489 if (it->second->GetRange(a, b)) | |
490 { | |
491 if (first) | |
492 { | |
493 minValue = a; | |
494 maxValue = b; | |
495 first = false; | |
496 } | |
497 else | |
498 { | |
499 minValue = std::min(a, minValue); | |
500 maxValue = std::max(b, maxValue); | |
501 } | |
502 } | |
503 } | |
504 | |
505 if (first) | |
506 { | |
507 minValue = 0; | |
508 maxValue = 0; | |
509 } | |
510 } | |
511 | |
512 | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
513 void RadiographyScene::ExportDicom(OrthancApiClient& orthanc, |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
514 const Orthanc::DicomMap& dicom, |
426 | 515 const std::string& parentOrthancId, |
408 | 516 double pixelSpacingX, |
517 double pixelSpacingY, | |
518 bool invert, | |
519 ImageInterpolation interpolation, | |
520 bool usePam) | |
521 { | |
426 | 522 Json::Value createDicomRequestContent; |
523 | |
427 | 524 ExportToCreateDicomRequest(createDicomRequestContent, dicom, pixelSpacingX, pixelSpacingY, invert, interpolation, usePam); |
426 | 525 |
526 if (!parentOrthancId.empty()) | |
527 { | |
528 createDicomRequestContent["Parent"] = parentOrthancId; | |
529 } | |
530 | |
531 orthanc.PostJsonAsyncExpectJson( | |
532 "/tools/create-dicom", createDicomRequestContent, | |
533 new Callable<RadiographyScene, OrthancApiClient::JsonResponseReadyMessage> | |
534 (*this, &RadiographyScene::OnDicomExported), | |
535 NULL, NULL); | |
536 } | |
537 | |
538 // Export using PAM is faster than using PNG, but requires Orthanc | |
539 // core >= 1.4.3 | |
427 | 540 void RadiographyScene::ExportToCreateDicomRequest(Json::Value& createDicomRequestContent, |
426 | 541 const Orthanc::DicomMap& dicom, |
542 double pixelSpacingX, | |
543 double pixelSpacingY, | |
544 bool invert, | |
545 ImageInterpolation interpolation, | |
546 bool usePam) | |
547 { | |
408 | 548 if (pixelSpacingX <= 0 || |
549 pixelSpacingY <= 0) | |
550 { | |
551 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
552 } | |
426 | 553 |
408 | 554 LOG(INFO) << "Exporting DICOM"; |
555 | |
556 Extent2D extent = GetSceneExtent(); | |
557 | |
558 int w = std::ceil(extent.GetWidth() / pixelSpacingX); | |
559 int h = std::ceil(extent.GetHeight() / pixelSpacingY); | |
560 | |
561 if (w < 0 || h < 0) | |
562 { | |
563 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
564 } | |
565 | |
566 Orthanc::Image layers(Orthanc::PixelFormat_Float32, | |
567 static_cast<unsigned int>(w), | |
568 static_cast<unsigned int>(h), false); | |
569 | |
409 | 570 AffineTransform2D view = AffineTransform2D::Combine( |
426 | 571 AffineTransform2D::CreateScaling(1.0 / pixelSpacingX, 1.0 / pixelSpacingY), |
572 AffineTransform2D::CreateOffset(-extent.GetX1(), -extent.GetY1())); | |
573 | |
408 | 574 Render(layers, view, interpolation); |
575 | |
576 Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16, | |
577 layers.GetWidth(), layers.GetHeight(), false); | |
578 Orthanc::ImageProcessing::Convert(rendered, layers); | |
579 | |
580 std::string base64; | |
581 | |
582 { | |
583 std::string content; | |
584 | |
585 if (usePam) | |
586 { | |
587 Orthanc::PamWriter writer; | |
588 writer.WriteToMemory(content, rendered); | |
589 } | |
590 else | |
591 { | |
592 Orthanc::PngWriter writer; | |
593 writer.WriteToMemory(content, rendered); | |
594 } | |
595 | |
596 Orthanc::Toolbox::EncodeBase64(base64, content); | |
597 } | |
598 | |
599 std::set<Orthanc::DicomTag> tags; | |
600 dicom.GetTags(tags); | |
601 | |
426 | 602 createDicomRequestContent["Tags"] = Json::objectValue; |
603 | |
408 | 604 for (std::set<Orthanc::DicomTag>::const_iterator |
426 | 605 tag = tags.begin(); tag != tags.end(); ++tag) |
408 | 606 { |
607 const Orthanc::DicomValue& value = dicom.GetValue(*tag); | |
608 if (!value.IsNull() && | |
609 !value.IsBinary()) | |
610 { | |
426 | 611 createDicomRequestContent["Tags"][tag->Format()] = value.GetContent(); |
408 | 612 } |
613 } | |
614 | |
426 | 615 createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] = |
616 (invert ? "MONOCHROME1" : "MONOCHROME2"); | |
408 | 617 |
618 // WARNING: The order of PixelSpacing is Y/X. We use "%0.8f" to | |
619 // avoid floating-point numbers to grow over 16 characters, | |
620 // which would be invalid according to DICOM standard | |
621 // ("dciodvfy" would complain). | |
622 char buf[32]; | |
623 sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX); | |
426 | 624 |
625 createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_PIXEL_SPACING.Format()] = buf; | |
408 | 626 |
627 float center, width; | |
628 if (GetWindowing(center, width)) | |
629 { | |
426 | 630 createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()] = |
631 boost::lexical_cast<std::string>(boost::math::iround(center)); | |
408 | 632 |
426 | 633 createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()] = |
634 boost::lexical_cast<std::string>(boost::math::iround(width)); | |
408 | 635 } |
636 | |
426 | 637 |
408 | 638 // This is Data URI scheme: https://en.wikipedia.org/wiki/Data_URI_scheme |
426 | 639 createDicomRequestContent["Content"] = ("data:" + |
640 std::string(usePam ? Orthanc::MIME_PAM : Orthanc::MIME_PNG) + | |
641 ";base64," + base64); | |
408 | 642 } |
643 | |
644 | |
645 void RadiographyScene::OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message) | |
646 { | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
647 LOG(INFO) << "DICOM export was successful: " |
408 | 648 << message.GetJson().toStyledString(); |
649 } | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
650 |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
651 |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
652 void RadiographyScene::OnDicomWebReceived(const IWebService::HttpRequestSuccessMessage& message) |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
653 { |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
654 LOG(INFO) << "DICOMweb WADO-RS received: " << message.GetAnswerSize() << " bytes"; |
418 | 655 |
656 const IWebService::HttpHeaders& h = message.GetAnswerHttpHeaders(); | |
657 for (IWebService::HttpHeaders::const_iterator | |
426 | 658 it = h.begin(); it != h.end(); ++it) |
418 | 659 { |
660 printf("[%s] = [%s]\n", it->first.c_str(), it->second.c_str()); | |
661 } | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
662 } |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
663 |
408 | 664 } |