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