Mercurial > hg > orthanc-stone
annotate Framework/Radiography/RadiographyScene.cpp @ 441:56ddca73396c am-vsol-upgrade
allow null callbacks for HTTP requests
author | Alain Mazy <alain@mazy.be> |
---|---|
date | Mon, 14 Jan 2019 11:27:26 +0100 |
parents | a750f11892ec |
children | 3c28542229a3 |
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 | |
439 | 5 * Copyright (C) 2017-2019 Osimis S.A., Belgium |
408 | 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 | |
432
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
169 PhotometricDisplayMode RadiographyScene::GetPreferredPhotomotricDisplayMode() const |
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
170 { |
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
171 // return the mode of the first layer who "cares" about its display mode (normaly, the one and only layer that is a DicomLayer) |
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
172 for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++) |
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
173 { |
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
174 if (it->second->GetPreferredPhotomotricDisplayMode() != PhotometricDisplayMode_Default) |
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
175 { |
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
176 return it->second->GetPreferredPhotomotricDisplayMode(); |
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
177 } |
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
178 } |
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
179 |
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
180 return PhotometricDisplayMode_Default; |
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
181 } |
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
182 |
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
183 |
430 | 184 void RadiographyScene::GetLayersIndexes(std::vector<size_t>& output) const |
185 { | |
186 for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++) | |
187 { | |
188 output.push_back(it->first); | |
189 } | |
190 } | |
191 | |
425 | 192 void RadiographyScene::RemoveLayer(size_t layerIndex) |
193 { | |
428
751fb354149e
ability to change the scene of the RadiographyWidget
am@osimis.io
parents:
426
diff
changeset
|
194 LOG(INFO) << "Removing layer: " << layerIndex; |
751fb354149e
ability to change the scene of the RadiographyWidget
am@osimis.io
parents:
426
diff
changeset
|
195 |
425 | 196 if (layerIndex > countLayers_) |
197 { | |
198 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
199 } | |
200 delete layers_[layerIndex]; | |
201 layers_.erase(layerIndex); | |
202 countLayers_--; | |
428
751fb354149e
ability to change the scene of the RadiographyWidget
am@osimis.io
parents:
426
diff
changeset
|
203 LOG(INFO) << "Removing layer, there are now : " << countLayers_ << " layers"; |
425 | 204 } |
205 | |
430 | 206 const RadiographyLayer& RadiographyScene::GetLayer(size_t layerIndex) const |
425 | 207 { |
208 if (layerIndex > countLayers_) | |
209 { | |
210 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
211 } | |
430 | 212 |
213 return *(layers_.at(layerIndex)); | |
425 | 214 } |
408 | 215 |
216 bool RadiographyScene::GetWindowing(float& center, | |
217 float& width) const | |
218 { | |
219 if (hasWindowing_) | |
220 { | |
221 center = windowingCenter_; | |
222 width = windowingWidth_; | |
223 return true; | |
224 } | |
225 else | |
226 { | |
227 return false; | |
228 } | |
229 } | |
230 | |
231 | |
232 void RadiographyScene::GetWindowingWithDefault(float& center, | |
233 float& width) const | |
234 { | |
235 if (!GetWindowing(center, width)) | |
236 { | |
237 center = 128; | |
238 width = 256; | |
239 } | |
240 } | |
241 | |
242 | |
243 void RadiographyScene::SetWindowing(float center, | |
244 float width) | |
245 { | |
246 hasWindowing_ = true; | |
247 windowingCenter_ = center; | |
248 windowingWidth_ = width; | |
249 } | |
250 | |
251 | |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
252 RadiographyLayer& RadiographyScene::LoadText(const Orthanc::Font& font, |
430 | 253 const std::string& utf8, |
254 RadiographyLayer::Geometry* geometry) | |
408 | 255 { |
430 | 256 std::auto_ptr<RadiographyTextLayer> alpha(new RadiographyTextLayer(*this)); |
408 | 257 alpha->LoadText(font, utf8); |
430 | 258 if (geometry != NULL) |
259 { | |
260 alpha->SetGeometry(*geometry); | |
261 } | |
408 | 262 |
263 return RegisterLayer(alpha.release()); | |
264 } | |
265 | |
426 | 266 |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
267 RadiographyLayer& RadiographyScene::LoadTestBlock(unsigned int width, |
430 | 268 unsigned int height, |
269 RadiographyLayer::Geometry* geometry) | |
408 | 270 { |
271 std::auto_ptr<Orthanc::Image> block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false)); | |
272 | |
273 for (unsigned int padding = 0; | |
274 (width > 2 * padding) && (height > 2 * padding); | |
275 padding++) | |
276 { | |
277 uint8_t color; | |
278 if (255 > 10 * padding) | |
279 { | |
280 color = 255 - 10 * padding; | |
281 } | |
282 else | |
283 { | |
284 color = 0; | |
285 } | |
286 | |
287 Orthanc::ImageAccessor region; | |
288 block->GetRegion(region, padding, padding, width - 2 * padding, height - 2 * padding); | |
289 Orthanc::ImageProcessing::Set(region, color); | |
290 } | |
291 | |
430 | 292 return LoadAlphaBitmap(block.release(), geometry); |
293 } | |
294 | |
295 RadiographyLayer& RadiographyScene::LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap, RadiographyLayer::Geometry *geometry) | |
296 { | |
297 std::auto_ptr<RadiographyAlphaLayer> alpha(new RadiographyAlphaLayer(*this)); | |
298 alpha->SetAlpha(bitmap); | |
299 if (geometry != NULL) | |
300 { | |
301 alpha->SetGeometry(*geometry); | |
302 } | |
408 | 303 |
304 return RegisterLayer(alpha.release()); | |
305 } | |
306 | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
307 RadiographyLayer& RadiographyScene::LoadDicomFrame(OrthancApiClient& orthanc, |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
308 const std::string& instance, |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
309 unsigned int frame, |
430 | 310 bool httpCompression, |
311 RadiographyLayer::Geometry* geometry) | |
408 | 312 { |
430 | 313 RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer)); |
314 layer.SetInstance(instance, frame); | |
315 | |
316 if (geometry != NULL) | |
317 { | |
318 layer.SetGeometry(*geometry); | |
319 } | |
408 | 320 |
321 { | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
322 IWebService::HttpHeaders headers; |
408 | 323 std::string uri = "/instances/" + instance + "/tags"; |
426 | 324 |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
325 orthanc.GetBinaryAsync( |
426 | 326 uri, headers, |
327 new Callable<RadiographyScene, OrthancApiClient::BinaryResponseReadyMessage> | |
328 (*this, &RadiographyScene::OnTagsReceived), NULL, | |
329 new Orthanc::SingleValueObject<size_t>(layer.GetIndex())); | |
408 | 330 } |
331 | |
332 { | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
333 IWebService::HttpHeaders headers; |
408 | 334 headers["Accept"] = "image/x-portable-arbitrarymap"; |
335 | |
336 if (httpCompression) | |
337 { | |
338 headers["Accept-Encoding"] = "gzip"; | |
339 } | |
426 | 340 |
408 | 341 std::string uri = ("/instances/" + instance + "/frames/" + |
342 boost::lexical_cast<std::string>(frame) + "/image-uint16"); | |
426 | 343 |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
344 orthanc.GetBinaryAsync( |
426 | 345 uri, headers, |
346 new Callable<RadiographyScene, OrthancApiClient::BinaryResponseReadyMessage> | |
347 (*this, &RadiographyScene::OnFrameReceived), NULL, | |
348 new Orthanc::SingleValueObject<size_t>(layer.GetIndex())); | |
408 | 349 } |
350 | |
351 return layer; | |
352 } | |
353 | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
354 |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
355 RadiographyLayer& RadiographyScene::LoadDicomWebFrame(IWebService& web) |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
356 { |
430 | 357 RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer); |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
358 |
426 | 359 |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
360 return layer; |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
361 } |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
362 |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
363 |
426 | 364 |
408 | 365 void RadiographyScene::OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) |
366 { | |
367 size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&> | |
426 | 368 (message.GetPayload()).GetValue(); |
408 | 369 |
370 LOG(INFO) << "JSON received: " << message.GetUri().c_str() | |
371 << " (" << message.GetAnswerSize() << " bytes) for layer " << index; | |
426 | 372 |
408 | 373 Layers::iterator layer = layers_.find(index); |
374 if (layer != layers_.end()) | |
375 { | |
376 assert(layer->second != NULL); | |
426 | 377 |
408 | 378 OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer(), message.GetAnswerSize()); |
430 | 379 dynamic_cast<RadiographyDicomLayer*>(layer->second)->SetDicomTags(dicom); |
408 | 380 |
381 float c, w; | |
382 if (!hasWindowing_ && | |
383 layer->second->GetDefaultWindowing(c, w)) | |
384 { | |
385 hasWindowing_ = true; | |
386 windowingCenter_ = c; | |
387 windowingWidth_ = w; | |
388 } | |
389 | |
430 | 390 EmitMessage(GeometryChangedMessage(*this, *(layer->second))); |
408 | 391 } |
392 } | |
426 | 393 |
408 | 394 |
395 void RadiographyScene::OnFrameReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) | |
396 { | |
397 size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&>(message.GetPayload()).GetValue(); | |
426 | 398 |
408 | 399 LOG(INFO) << "DICOM frame received: " << message.GetUri().c_str() |
400 << " (" << message.GetAnswerSize() << " bytes) for layer " << index; | |
426 | 401 |
408 | 402 Layers::iterator layer = layers_.find(index); |
403 if (layer != layers_.end()) | |
404 { | |
405 assert(layer->second != NULL); | |
406 | |
407 std::string content; | |
408 if (message.GetAnswerSize() > 0) | |
409 { | |
410 content.assign(reinterpret_cast<const char*>(message.GetAnswer()), message.GetAnswerSize()); | |
411 } | |
426 | 412 |
408 | 413 std::auto_ptr<Orthanc::PamReader> reader(new Orthanc::PamReader); |
414 reader->ReadFromMemory(content); | |
430 | 415 dynamic_cast<RadiographyDicomLayer*>(layer->second)->SetSourceImage(reader.release()); |
408 | 416 |
430 | 417 EmitMessage(ContentChangedMessage(*this, *(layer->second))); |
408 | 418 } |
419 } | |
420 | |
421 | |
422 Extent2D RadiographyScene::GetSceneExtent() const | |
423 { | |
424 Extent2D extent; | |
425 | |
426 for (Layers::const_iterator it = layers_.begin(); | |
427 it != layers_.end(); ++it) | |
428 { | |
429 assert(it->second != NULL); | |
430 extent.Union(it->second->GetExtent()); | |
431 } | |
432 | |
433 return extent; | |
434 } | |
426 | 435 |
408 | 436 |
437 void RadiographyScene::Render(Orthanc::ImageAccessor& buffer, | |
409 | 438 const AffineTransform2D& viewTransform, |
408 | 439 ImageInterpolation interpolation) const |
440 { | |
441 // Render layers in the background-to-foreground order | |
442 for (size_t index = 0; index < countLayers_; index++) | |
443 { | |
444 Layers::const_iterator it = layers_.find(index); | |
445 if (it != layers_.end()) | |
446 { | |
447 assert(it->second != NULL); | |
448 it->second->Render(buffer, viewTransform, interpolation); | |
449 } | |
450 } | |
451 } | |
452 | |
453 | |
454 bool RadiographyScene::LookupLayer(size_t& index /* out */, | |
455 double x, | |
456 double y) const | |
457 { | |
458 // Render layers in the foreground-to-background order | |
459 for (size_t i = countLayers_; i > 0; i--) | |
460 { | |
461 index = i - 1; | |
462 Layers::const_iterator it = layers_.find(index); | |
463 if (it != layers_.end()) | |
464 { | |
465 assert(it->second != NULL); | |
466 if (it->second->Contains(x, y)) | |
467 { | |
468 return true; | |
469 } | |
470 } | |
471 } | |
472 | |
473 return false; | |
474 } | |
475 | |
426 | 476 |
408 | 477 void RadiographyScene::DrawBorder(CairoContext& context, |
478 unsigned int layer, | |
479 double zoom) | |
480 { | |
481 Layers::const_iterator found = layers_.find(layer); | |
426 | 482 |
408 | 483 if (found != layers_.end()) |
484 { | |
485 context.SetSourceColor(255, 0, 0); | |
486 found->second->DrawBorders(context, zoom); | |
487 } | |
488 } | |
489 | |
490 | |
491 void RadiographyScene::GetRange(float& minValue, | |
492 float& maxValue) const | |
493 { | |
494 bool first = true; | |
426 | 495 |
408 | 496 for (Layers::const_iterator it = layers_.begin(); |
497 it != layers_.end(); it++) | |
498 { | |
499 assert(it->second != NULL); | |
500 | |
501 float a, b; | |
502 if (it->second->GetRange(a, b)) | |
503 { | |
504 if (first) | |
505 { | |
506 minValue = a; | |
507 maxValue = b; | |
508 first = false; | |
509 } | |
510 else | |
511 { | |
512 minValue = std::min(a, minValue); | |
513 maxValue = std::max(b, maxValue); | |
514 } | |
515 } | |
516 } | |
517 | |
518 if (first) | |
519 { | |
520 minValue = 0; | |
521 maxValue = 0; | |
522 } | |
523 } | |
524 | |
525 | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
526 void RadiographyScene::ExportDicom(OrthancApiClient& orthanc, |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
527 const Orthanc::DicomMap& dicom, |
426 | 528 const std::string& parentOrthancId, |
408 | 529 double pixelSpacingX, |
530 double pixelSpacingY, | |
531 bool invert, | |
532 ImageInterpolation interpolation, | |
533 bool usePam) | |
534 { | |
426 | 535 Json::Value createDicomRequestContent; |
536 | |
427 | 537 ExportToCreateDicomRequest(createDicomRequestContent, dicom, pixelSpacingX, pixelSpacingY, invert, interpolation, usePam); |
426 | 538 |
539 if (!parentOrthancId.empty()) | |
540 { | |
541 createDicomRequestContent["Parent"] = parentOrthancId; | |
542 } | |
543 | |
544 orthanc.PostJsonAsyncExpectJson( | |
545 "/tools/create-dicom", createDicomRequestContent, | |
546 new Callable<RadiographyScene, OrthancApiClient::JsonResponseReadyMessage> | |
547 (*this, &RadiographyScene::OnDicomExported), | |
548 NULL, NULL); | |
549 } | |
550 | |
551 // Export using PAM is faster than using PNG, but requires Orthanc | |
552 // core >= 1.4.3 | |
427 | 553 void RadiographyScene::ExportToCreateDicomRequest(Json::Value& createDicomRequestContent, |
426 | 554 const Orthanc::DicomMap& dicom, |
555 double pixelSpacingX, | |
556 double pixelSpacingY, | |
557 bool invert, | |
558 ImageInterpolation interpolation, | |
559 bool usePam) | |
560 { | |
408 | 561 if (pixelSpacingX <= 0 || |
562 pixelSpacingY <= 0) | |
563 { | |
564 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
565 } | |
426 | 566 |
408 | 567 LOG(INFO) << "Exporting DICOM"; |
568 | |
569 Extent2D extent = GetSceneExtent(); | |
570 | |
571 int w = std::ceil(extent.GetWidth() / pixelSpacingX); | |
572 int h = std::ceil(extent.GetHeight() / pixelSpacingY); | |
573 | |
574 if (w < 0 || h < 0) | |
575 { | |
576 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
577 } | |
578 | |
579 Orthanc::Image layers(Orthanc::PixelFormat_Float32, | |
580 static_cast<unsigned int>(w), | |
581 static_cast<unsigned int>(h), false); | |
582 | |
409 | 583 AffineTransform2D view = AffineTransform2D::Combine( |
426 | 584 AffineTransform2D::CreateScaling(1.0 / pixelSpacingX, 1.0 / pixelSpacingY), |
585 AffineTransform2D::CreateOffset(-extent.GetX1(), -extent.GetY1())); | |
586 | |
432
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
587 // wipe background before rendering |
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
588 Orthanc::ImageProcessing::Set(layers, 0); |
4eb96c6b4e96
improved handling of MONOCHROME1, background and invertion
am@osimis.io
parents:
431
diff
changeset
|
589 |
408 | 590 Render(layers, view, interpolation); |
591 | |
592 Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16, | |
593 layers.GetWidth(), layers.GetHeight(), false); | |
594 Orthanc::ImageProcessing::Convert(rendered, layers); | |
595 | |
596 std::string base64; | |
597 | |
598 { | |
599 std::string content; | |
600 | |
601 if (usePam) | |
602 { | |
603 Orthanc::PamWriter writer; | |
604 writer.WriteToMemory(content, rendered); | |
605 } | |
606 else | |
607 { | |
608 Orthanc::PngWriter writer; | |
609 writer.WriteToMemory(content, rendered); | |
610 } | |
611 | |
612 Orthanc::Toolbox::EncodeBase64(base64, content); | |
613 } | |
614 | |
615 std::set<Orthanc::DicomTag> tags; | |
616 dicom.GetTags(tags); | |
617 | |
426 | 618 createDicomRequestContent["Tags"] = Json::objectValue; |
619 | |
408 | 620 for (std::set<Orthanc::DicomTag>::const_iterator |
426 | 621 tag = tags.begin(); tag != tags.end(); ++tag) |
408 | 622 { |
623 const Orthanc::DicomValue& value = dicom.GetValue(*tag); | |
624 if (!value.IsNull() && | |
625 !value.IsBinary()) | |
626 { | |
426 | 627 createDicomRequestContent["Tags"][tag->Format()] = value.GetContent(); |
408 | 628 } |
629 } | |
630 | |
436
04711a2e12cd
fix crop + export photometric interpretation correctly
am@osimis.io
parents:
432
diff
changeset
|
631 PhotometricDisplayMode photometricMode = GetPreferredPhotomotricDisplayMode(); |
04711a2e12cd
fix crop + export photometric interpretation correctly
am@osimis.io
parents:
432
diff
changeset
|
632 if ((invert && photometricMode != PhotometricDisplayMode_Monochrome2) || |
04711a2e12cd
fix crop + export photometric interpretation correctly
am@osimis.io
parents:
432
diff
changeset
|
633 (!invert && photometricMode == PhotometricDisplayMode_Monochrome1)) |
04711a2e12cd
fix crop + export photometric interpretation correctly
am@osimis.io
parents:
432
diff
changeset
|
634 { |
04711a2e12cd
fix crop + export photometric interpretation correctly
am@osimis.io
parents:
432
diff
changeset
|
635 createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] = "MONOCHROME1"; |
04711a2e12cd
fix crop + export photometric interpretation correctly
am@osimis.io
parents:
432
diff
changeset
|
636 } |
04711a2e12cd
fix crop + export photometric interpretation correctly
am@osimis.io
parents:
432
diff
changeset
|
637 else |
04711a2e12cd
fix crop + export photometric interpretation correctly
am@osimis.io
parents:
432
diff
changeset
|
638 { |
04711a2e12cd
fix crop + export photometric interpretation correctly
am@osimis.io
parents:
432
diff
changeset
|
639 createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] = "MONOCHROME2"; |
04711a2e12cd
fix crop + export photometric interpretation correctly
am@osimis.io
parents:
432
diff
changeset
|
640 } |
408 | 641 |
642 // WARNING: The order of PixelSpacing is Y/X. We use "%0.8f" to | |
643 // avoid floating-point numbers to grow over 16 characters, | |
644 // which would be invalid according to DICOM standard | |
645 // ("dciodvfy" would complain). | |
646 char buf[32]; | |
647 sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX); | |
426 | 648 |
649 createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_PIXEL_SPACING.Format()] = buf; | |
408 | 650 |
651 float center, width; | |
652 if (GetWindowing(center, width)) | |
653 { | |
426 | 654 createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()] = |
655 boost::lexical_cast<std::string>(boost::math::iround(center)); | |
408 | 656 |
426 | 657 createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()] = |
658 boost::lexical_cast<std::string>(boost::math::iround(width)); | |
408 | 659 } |
660 | |
426 | 661 |
408 | 662 // This is Data URI scheme: https://en.wikipedia.org/wiki/Data_URI_scheme |
426 | 663 createDicomRequestContent["Content"] = ("data:" + |
664 std::string(usePam ? Orthanc::MIME_PAM : Orthanc::MIME_PNG) + | |
665 ";base64," + base64); | |
408 | 666 } |
667 | |
668 | |
669 void RadiographyScene::OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message) | |
670 { | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
671 LOG(INFO) << "DICOM export was successful: " |
408 | 672 << message.GetJson().toStyledString(); |
673 } | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
674 |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
675 |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
676 void RadiographyScene::OnDicomWebReceived(const IWebService::HttpRequestSuccessMessage& message) |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
677 { |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
678 LOG(INFO) << "DICOMweb WADO-RS received: " << message.GetAnswerSize() << " bytes"; |
418 | 679 |
680 const IWebService::HttpHeaders& h = message.GetAnswerHttpHeaders(); | |
681 for (IWebService::HttpHeaders::const_iterator | |
426 | 682 it = h.begin(); it != h.end(); ++it) |
418 | 683 { |
684 printf("[%s] = [%s]\n", it->first.c_str(), it->second.c_str()); | |
685 } | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
686 } |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
687 |
408 | 688 } |