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
|
|
24 #include "../Toolbox/ImageGeometry.h"
|
|
25 #include "../Toolbox/DicomFrameConverter.h"
|
|
26
|
|
27 #include <Core/Images/Image.h>
|
|
28 #include <Core/Images/ImageProcessing.h>
|
|
29 #include <Core/Images/PamReader.h>
|
|
30 #include <Core/Images/PamWriter.h>
|
|
31 #include <Core/Images/PngWriter.h>
|
|
32 #include <Core/OrthancException.h>
|
|
33 #include <Core/Toolbox.h>
|
|
34 #include <Plugins/Samples/Common/DicomDatasetReader.h>
|
|
35 #include <Plugins/Samples/Common/FullOrthancDataset.h>
|
|
36
|
|
37
|
|
38 namespace OrthancStone
|
|
39 {
|
|
40 static double Square(double x)
|
|
41 {
|
|
42 return x * x;
|
|
43 }
|
|
44
|
|
45
|
|
46 static Matrix CreateOffsetMatrix(double dx,
|
|
47 double dy)
|
|
48 {
|
|
49 Matrix m = LinearAlgebra::IdentityMatrix(3);
|
|
50 m(0, 2) = dx;
|
|
51 m(1, 2) = dy;
|
|
52 return m;
|
|
53 }
|
|
54
|
|
55
|
|
56 static Matrix CreateScalingMatrix(double sx,
|
|
57 double sy)
|
|
58 {
|
|
59 Matrix m = LinearAlgebra::IdentityMatrix(3);
|
|
60 m(0, 0) = sx;
|
|
61 m(1, 1) = sy;
|
|
62 return m;
|
|
63 }
|
|
64
|
|
65
|
|
66 static Matrix CreateRotationMatrix(double angle)
|
|
67 {
|
|
68 Matrix m;
|
|
69 const double v[] = { cos(angle), -sin(angle), 0,
|
|
70 sin(angle), cos(angle), 0,
|
|
71 0, 0, 1 };
|
|
72 LinearAlgebra::FillMatrix(m, 3, 3, v);
|
|
73 return m;
|
|
74 }
|
|
75
|
|
76
|
|
77 static void ApplyTransform(double& x /* inout */,
|
|
78 double& y /* inout */,
|
|
79 const Matrix& transform)
|
|
80 {
|
|
81 Vector p;
|
|
82 LinearAlgebra::AssignVector(p, x, y, 1);
|
|
83
|
|
84 Vector q = LinearAlgebra::Product(transform, p);
|
|
85
|
|
86 if (!LinearAlgebra::IsNear(q[2], 1.0))
|
|
87 {
|
|
88 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
|
|
89 }
|
|
90 else
|
|
91 {
|
|
92 x = q[0];
|
|
93 y = q[1];
|
|
94 }
|
|
95 }
|
|
96
|
|
97
|
|
98
|
|
99 void RadiographyScene::Layer::UpdateTransform()
|
|
100 {
|
|
101 transform_ = CreateScalingMatrix(pixelSpacingX_, pixelSpacingY_);
|
|
102
|
|
103 double centerX, centerY;
|
|
104 GetCenter(centerX, centerY);
|
|
105
|
|
106 transform_ = LinearAlgebra::Product(
|
|
107 CreateOffsetMatrix(panX_ + centerX, panY_ + centerY),
|
|
108 CreateRotationMatrix(angle_),
|
|
109 CreateOffsetMatrix(-centerX, -centerY),
|
|
110 transform_);
|
|
111
|
|
112 LinearAlgebra::InvertMatrix(transformInverse_, transform_);
|
|
113 }
|
|
114
|
|
115
|
|
116 void RadiographyScene::Layer::AddToExtent(Extent2D& extent,
|
|
117 double x,
|
|
118 double y) const
|
|
119 {
|
|
120 ApplyTransform(x, y, transform_);
|
|
121 extent.AddPoint(x, y);
|
|
122 }
|
|
123
|
|
124
|
|
125 void RadiographyScene::Layer::GetCornerInternal(double& x,
|
|
126 double& y,
|
|
127 Corner corner,
|
|
128 unsigned int cropX,
|
|
129 unsigned int cropY,
|
|
130 unsigned int cropWidth,
|
|
131 unsigned int cropHeight) const
|
|
132 {
|
|
133 double dx = static_cast<double>(cropX);
|
|
134 double dy = static_cast<double>(cropY);
|
|
135 double dwidth = static_cast<double>(cropWidth);
|
|
136 double dheight = static_cast<double>(cropHeight);
|
|
137
|
|
138 switch (corner)
|
|
139 {
|
|
140 case Corner_TopLeft:
|
|
141 x = dx;
|
|
142 y = dy;
|
|
143 break;
|
|
144
|
|
145 case Corner_TopRight:
|
|
146 x = dx + dwidth;
|
|
147 y = dy;
|
|
148 break;
|
|
149
|
|
150 case Corner_BottomLeft:
|
|
151 x = dx;
|
|
152 y = dy + dheight;
|
|
153 break;
|
|
154
|
|
155 case Corner_BottomRight:
|
|
156 x = dx + dwidth;
|
|
157 y = dy + dheight;
|
|
158 break;
|
|
159
|
|
160 default:
|
|
161 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
|
|
162 }
|
|
163
|
|
164 ApplyTransform(x, y, transform_);
|
|
165 }
|
|
166
|
|
167
|
|
168 bool RadiographyScene::Layer::Contains(double x,
|
|
169 double y) const
|
|
170 {
|
|
171 ApplyTransform(x, y, transformInverse_);
|
|
172
|
|
173 unsigned int cropX, cropY, cropWidth, cropHeight;
|
|
174 GetCrop(cropX, cropY, cropWidth, cropHeight);
|
|
175
|
|
176 return (x >= cropX && x <= cropX + cropWidth &&
|
|
177 y >= cropY && y <= cropY + cropHeight);
|
|
178 }
|
|
179
|
|
180
|
|
181 void RadiographyScene::Layer::DrawBorders(CairoContext& context,
|
|
182 double zoom)
|
|
183 {
|
|
184 unsigned int cx, cy, width, height;
|
|
185 GetCrop(cx, cy, width, height);
|
|
186
|
|
187 double dx = static_cast<double>(cx);
|
|
188 double dy = static_cast<double>(cy);
|
|
189 double dwidth = static_cast<double>(width);
|
|
190 double dheight = static_cast<double>(height);
|
|
191
|
|
192 cairo_t* cr = context.GetObject();
|
|
193 cairo_set_line_width(cr, 2.0 / zoom);
|
|
194
|
|
195 double x, y;
|
|
196 x = dx;
|
|
197 y = dy;
|
|
198 ApplyTransform(x, y, transform_);
|
|
199 cairo_move_to(cr, x, y);
|
|
200
|
|
201 x = dx + dwidth;
|
|
202 y = dy;
|
|
203 ApplyTransform(x, y, transform_);
|
|
204 cairo_line_to(cr, x, y);
|
|
205
|
|
206 x = dx + dwidth;
|
|
207 y = dy + dheight;
|
|
208 ApplyTransform(x, y, transform_);
|
|
209 cairo_line_to(cr, x, y);
|
|
210
|
|
211 x = dx;
|
|
212 y = dy + dheight;
|
|
213 ApplyTransform(x, y, transform_);
|
|
214 cairo_line_to(cr, x, y);
|
|
215
|
|
216 x = dx;
|
|
217 y = dy;
|
|
218 ApplyTransform(x, y, transform_);
|
|
219 cairo_line_to(cr, x, y);
|
|
220
|
|
221 cairo_stroke(cr);
|
|
222 }
|
|
223
|
|
224
|
|
225 RadiographyScene::Layer::Layer() :
|
|
226 index_(0),
|
|
227 hasSize_(false),
|
|
228 width_(0),
|
|
229 height_(0),
|
|
230 hasCrop_(false),
|
|
231 pixelSpacingX_(1),
|
|
232 pixelSpacingY_(1),
|
|
233 panX_(0),
|
|
234 panY_(0),
|
|
235 angle_(0),
|
|
236 resizeable_(false)
|
|
237 {
|
|
238 UpdateTransform();
|
|
239 }
|
|
240
|
|
241
|
|
242 void RadiographyScene::Layer::SetCrop(unsigned int x,
|
|
243 unsigned int y,
|
|
244 unsigned int width,
|
|
245 unsigned int height)
|
|
246 {
|
|
247 if (!hasSize_)
|
|
248 {
|
|
249 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
|
|
250 }
|
|
251
|
|
252 if (x + width > width_ ||
|
|
253 y + height > height_)
|
|
254 {
|
|
255 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
|
|
256 }
|
|
257
|
|
258 hasCrop_ = true;
|
|
259 cropX_ = x;
|
|
260 cropY_ = y;
|
|
261 cropWidth_ = width;
|
|
262 cropHeight_ = height;
|
|
263
|
|
264 UpdateTransform();
|
|
265 }
|
|
266
|
|
267
|
|
268 void RadiographyScene::Layer::GetCrop(unsigned int& x,
|
|
269 unsigned int& y,
|
|
270 unsigned int& width,
|
|
271 unsigned int& height) const
|
|
272 {
|
|
273 if (hasCrop_)
|
|
274 {
|
|
275 x = cropX_;
|
|
276 y = cropY_;
|
|
277 width = cropWidth_;
|
|
278 height = cropHeight_;
|
|
279 }
|
|
280 else
|
|
281 {
|
|
282 x = 0;
|
|
283 y = 0;
|
|
284 width = width_;
|
|
285 height = height_;
|
|
286 }
|
|
287 }
|
|
288
|
|
289
|
|
290 void RadiographyScene::Layer::SetAngle(double angle)
|
|
291 {
|
|
292 angle_ = angle;
|
|
293 UpdateTransform();
|
|
294 }
|
|
295
|
|
296
|
|
297 void RadiographyScene::Layer::SetSize(unsigned int width,
|
|
298 unsigned int height)
|
|
299 {
|
|
300 if (hasSize_ &&
|
|
301 (width != width_ ||
|
|
302 height != height_))
|
|
303 {
|
|
304 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
|
|
305 }
|
|
306
|
|
307 hasSize_ = true;
|
|
308 width_ = width;
|
|
309 height_ = height;
|
|
310
|
|
311 UpdateTransform();
|
|
312 }
|
|
313
|
|
314
|
|
315 Extent2D RadiographyScene::Layer::GetExtent() const
|
|
316 {
|
|
317 Extent2D extent;
|
|
318
|
|
319 unsigned int x, y, width, height;
|
|
320 GetCrop(x, y, width, height);
|
|
321
|
|
322 double dx = static_cast<double>(x);
|
|
323 double dy = static_cast<double>(y);
|
|
324 double dwidth = static_cast<double>(width);
|
|
325 double dheight = static_cast<double>(height);
|
|
326
|
|
327 AddToExtent(extent, dx, dy);
|
|
328 AddToExtent(extent, dx + dwidth, dy);
|
|
329 AddToExtent(extent, dx, dy + dheight);
|
|
330 AddToExtent(extent, dx + dwidth, dy + dheight);
|
|
331
|
|
332 return extent;
|
|
333 }
|
|
334
|
|
335
|
|
336 bool RadiographyScene::Layer::GetPixel(unsigned int& imageX,
|
|
337 unsigned int& imageY,
|
|
338 double sceneX,
|
|
339 double sceneY) const
|
|
340 {
|
|
341 if (width_ == 0 ||
|
|
342 height_ == 0)
|
|
343 {
|
|
344 return false;
|
|
345 }
|
|
346 else
|
|
347 {
|
|
348 ApplyTransform(sceneX, sceneY, transformInverse_);
|
|
349
|
|
350 int x = static_cast<int>(std::floor(sceneX));
|
|
351 int y = static_cast<int>(std::floor(sceneY));
|
|
352
|
|
353 if (x < 0)
|
|
354 {
|
|
355 imageX = 0;
|
|
356 }
|
|
357 else if (x >= static_cast<int>(width_))
|
|
358 {
|
|
359 imageX = width_;
|
|
360 }
|
|
361 else
|
|
362 {
|
|
363 imageX = static_cast<unsigned int>(x);
|
|
364 }
|
|
365
|
|
366 if (y < 0)
|
|
367 {
|
|
368 imageY = 0;
|
|
369 }
|
|
370 else if (y >= static_cast<int>(height_))
|
|
371 {
|
|
372 imageY = height_;
|
|
373 }
|
|
374 else
|
|
375 {
|
|
376 imageY = static_cast<unsigned int>(y);
|
|
377 }
|
|
378
|
|
379 return true;
|
|
380 }
|
|
381 }
|
|
382
|
|
383
|
|
384 void RadiographyScene::Layer::SetPan(double x,
|
|
385 double y)
|
|
386 {
|
|
387 panX_ = x;
|
|
388 panY_ = y;
|
|
389 UpdateTransform();
|
|
390 }
|
|
391
|
|
392
|
|
393 void RadiographyScene::Layer::SetPixelSpacing(double x,
|
|
394 double y)
|
|
395 {
|
|
396 pixelSpacingX_ = x;
|
|
397 pixelSpacingY_ = y;
|
|
398 UpdateTransform();
|
|
399 }
|
|
400
|
|
401
|
|
402 void RadiographyScene::Layer::GetCenter(double& centerX,
|
|
403 double& centerY) const
|
|
404 {
|
|
405 centerX = static_cast<double>(width_) / 2.0;
|
|
406 centerY = static_cast<double>(height_) / 2.0;
|
|
407 ApplyTransform(centerX, centerY, transform_);
|
|
408 }
|
|
409
|
|
410
|
|
411 void RadiographyScene::Layer::GetCorner(double& x /* out */,
|
|
412 double& y /* out */,
|
|
413 Corner corner) const
|
|
414 {
|
|
415 unsigned int cropX, cropY, cropWidth, cropHeight;
|
|
416 GetCrop(cropX, cropY, cropWidth, cropHeight);
|
|
417 GetCornerInternal(x, y, corner, cropX, cropY, cropWidth, cropHeight);
|
|
418 }
|
|
419
|
|
420
|
|
421 bool RadiographyScene::Layer::LookupCorner(Corner& corner /* out */,
|
|
422 double x,
|
|
423 double y,
|
|
424 double zoom,
|
|
425 double viewportDistance) const
|
|
426 {
|
|
427 static const Corner CORNERS[] = {
|
|
428 Corner_TopLeft,
|
|
429 Corner_TopRight,
|
|
430 Corner_BottomLeft,
|
|
431 Corner_BottomRight
|
|
432 };
|
|
433
|
|
434 unsigned int cropX, cropY, cropWidth, cropHeight;
|
|
435 GetCrop(cropX, cropY, cropWidth, cropHeight);
|
|
436
|
|
437 double threshold = Square(viewportDistance / zoom);
|
|
438
|
|
439 for (size_t i = 0; i < 4; i++)
|
|
440 {
|
|
441 double cx, cy;
|
|
442 GetCornerInternal(cx, cy, CORNERS[i], cropX, cropY, cropWidth, cropHeight);
|
|
443
|
|
444 double d = Square(cx - x) + Square(cy - y);
|
|
445
|
|
446 if (d <= threshold)
|
|
447 {
|
|
448 corner = CORNERS[i];
|
|
449 return true;
|
|
450 }
|
|
451 }
|
|
452
|
|
453 return false;
|
|
454 }
|
|
455
|
|
456
|
|
457
|
|
458 RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene,
|
|
459 size_t index) :
|
|
460 scene_(scene),
|
|
461 index_(index)
|
|
462 {
|
|
463 Layers::iterator layer = scene.layers_.find(index);
|
|
464 if (layer == scene.layers_.end())
|
|
465 {
|
|
466 layer_ = NULL;
|
|
467 }
|
|
468 else
|
|
469 {
|
|
470 assert(layer->second != NULL);
|
|
471 layer_ = layer->second;
|
|
472 }
|
|
473 }
|
|
474
|
|
475
|
|
476 RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene,
|
|
477 double x,
|
|
478 double y) :
|
|
479 scene_(scene),
|
|
480 index_(0) // Dummy initialization
|
|
481 {
|
|
482 if (scene.LookupLayer(index_, x, y))
|
|
483 {
|
|
484 Layers::iterator layer = scene.layers_.find(index_);
|
|
485
|
|
486 if (layer == scene.layers_.end())
|
|
487 {
|
|
488 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
|
|
489 }
|
|
490 else
|
|
491 {
|
|
492 assert(layer->second != NULL);
|
|
493 layer_ = layer->second;
|
|
494 }
|
|
495 }
|
|
496 else
|
|
497 {
|
|
498 layer_ = NULL;
|
|
499 }
|
|
500 }
|
|
501
|
|
502
|
|
503 RadiographyScene& RadiographyScene::LayerAccessor::GetScene() const
|
|
504 {
|
|
505 if (IsValid())
|
|
506 {
|
|
507 return scene_;
|
|
508 }
|
|
509 else
|
|
510 {
|
|
511 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
|
|
512 }
|
|
513 }
|
|
514
|
|
515
|
|
516 size_t RadiographyScene::LayerAccessor::GetIndex() const
|
|
517 {
|
|
518 if (IsValid())
|
|
519 {
|
|
520 return index_;
|
|
521 }
|
|
522 else
|
|
523 {
|
|
524 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
|
|
525 }
|
|
526 }
|
|
527
|
|
528
|
|
529 RadiographyScene::Layer& RadiographyScene::LayerAccessor::GetLayer() const
|
|
530 {
|
|
531 if (IsValid())
|
|
532 {
|
|
533 return *layer_;
|
|
534 }
|
|
535 else
|
|
536 {
|
|
537 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
|
|
538 }
|
|
539 }
|
|
540
|
|
541
|
|
542
|
|
543 class RadiographyScene::AlphaLayer : public Layer
|
|
544 {
|
|
545 private:
|
|
546 const RadiographyScene& scene_;
|
|
547 std::auto_ptr<Orthanc::ImageAccessor> alpha_; // Grayscale8
|
|
548 bool useWindowing_;
|
|
549 float foreground_;
|
|
550
|
|
551 public:
|
|
552 AlphaLayer(const RadiographyScene& scene) :
|
|
553 scene_(scene),
|
|
554 useWindowing_(true),
|
|
555 foreground_(0)
|
|
556 {
|
|
557 }
|
|
558
|
|
559
|
|
560 void SetForegroundValue(float foreground)
|
|
561 {
|
|
562 useWindowing_ = false;
|
|
563 foreground_ = foreground;
|
|
564 }
|
|
565
|
|
566
|
|
567 void SetAlpha(Orthanc::ImageAccessor* image)
|
|
568 {
|
|
569 std::auto_ptr<Orthanc::ImageAccessor> raii(image);
|
|
570
|
|
571 if (image == NULL)
|
|
572 {
|
|
573 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
|
|
574 }
|
|
575
|
|
576 if (image->GetFormat() != Orthanc::PixelFormat_Grayscale8)
|
|
577 {
|
|
578 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
|
|
579 }
|
|
580
|
|
581 SetSize(image->GetWidth(), image->GetHeight());
|
|
582 alpha_ = raii;
|
|
583 }
|
|
584
|
|
585
|
|
586 void LoadText(const Orthanc::Font& font,
|
|
587 const std::string& utf8)
|
|
588 {
|
|
589 SetAlpha(font.RenderAlpha(utf8));
|
|
590 }
|
|
591
|
|
592
|
|
593 virtual bool GetDefaultWindowing(float& center,
|
|
594 float& width) const
|
|
595 {
|
|
596 return false;
|
|
597 }
|
|
598
|
|
599
|
|
600 virtual void Render(Orthanc::ImageAccessor& buffer,
|
|
601 const Matrix& viewTransform,
|
|
602 ImageInterpolation interpolation) const
|
|
603 {
|
|
604 if (alpha_.get() == NULL)
|
|
605 {
|
|
606 return;
|
|
607 }
|
|
608
|
|
609 if (buffer.GetFormat() != Orthanc::PixelFormat_Float32)
|
|
610 {
|
|
611 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
|
|
612 }
|
|
613
|
|
614 unsigned int cropX, cropY, cropWidth, cropHeight;
|
|
615 GetCrop(cropX, cropY, cropWidth, cropHeight);
|
|
616
|
|
617 Matrix m = LinearAlgebra::Product(viewTransform,
|
|
618 GetTransform(),
|
|
619 CreateOffsetMatrix(cropX, cropY));
|
|
620
|
|
621 Orthanc::ImageAccessor cropped;
|
|
622 alpha_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
|
|
623
|
|
624 Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false);
|
|
625 ApplyProjectiveTransform(tmp, cropped, m, interpolation, true /* clear */);
|
|
626
|
|
627 // Blit
|
|
628 const unsigned int width = buffer.GetWidth();
|
|
629 const unsigned int height = buffer.GetHeight();
|
|
630
|
|
631 float value = foreground_;
|
|
632
|
|
633 if (useWindowing_)
|
|
634 {
|
|
635 float center, width;
|
|
636 if (scene_.GetWindowing(center, width))
|
|
637 {
|
|
638 value = center + width / 2.0f;
|
|
639 }
|
|
640 }
|
|
641
|
|
642 for (unsigned int y = 0; y < height; y++)
|
|
643 {
|
|
644 float *q = reinterpret_cast<float*>(buffer.GetRow(y));
|
|
645 const uint8_t *p = reinterpret_cast<uint8_t*>(tmp.GetRow(y));
|
|
646
|
|
647 for (unsigned int x = 0; x < width; x++, p++, q++)
|
|
648 {
|
|
649 float a = static_cast<float>(*p) / 255.0f;
|
|
650
|
|
651 *q = (a * value + (1.0f - a) * (*q));
|
|
652 }
|
|
653 }
|
|
654 }
|
|
655
|
|
656
|
|
657 virtual bool GetRange(float& minValue,
|
|
658 float& maxValue) const
|
|
659 {
|
|
660 if (useWindowing_)
|
|
661 {
|
|
662 return false;
|
|
663 }
|
|
664 else
|
|
665 {
|
|
666 minValue = 0;
|
|
667 maxValue = 0;
|
|
668
|
|
669 if (foreground_ < 0)
|
|
670 {
|
|
671 minValue = foreground_;
|
|
672 }
|
|
673
|
|
674 if (foreground_ > 0)
|
|
675 {
|
|
676 maxValue = foreground_;
|
|
677 }
|
|
678
|
|
679 return true;
|
|
680 }
|
|
681 }
|
|
682 };
|
|
683
|
|
684
|
|
685
|
|
686 class RadiographyScene::DicomLayer : public Layer
|
|
687 {
|
|
688 private:
|
|
689 std::auto_ptr<Orthanc::ImageAccessor> source_; // Content of PixelData
|
|
690 std::auto_ptr<DicomFrameConverter> converter_;
|
|
691 std::auto_ptr<Orthanc::ImageAccessor> converted_; // Float32
|
|
692
|
|
693 static OrthancPlugins::DicomTag ConvertTag(const Orthanc::DicomTag& tag)
|
|
694 {
|
|
695 return OrthancPlugins::DicomTag(tag.GetGroup(), tag.GetElement());
|
|
696 }
|
|
697
|
|
698
|
|
699 void ApplyConverter()
|
|
700 {
|
|
701 if (source_.get() != NULL &&
|
|
702 converter_.get() != NULL)
|
|
703 {
|
|
704 converted_.reset(converter_->ConvertFrame(*source_));
|
|
705 }
|
|
706 }
|
|
707
|
|
708 public:
|
|
709 void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset)
|
|
710 {
|
|
711 converter_.reset(new DicomFrameConverter);
|
|
712 converter_->ReadParameters(dataset);
|
|
713 ApplyConverter();
|
|
714
|
|
715 std::string tmp;
|
|
716 Vector pixelSpacing;
|
|
717
|
|
718 if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PIXEL_SPACING)) &&
|
|
719 LinearAlgebra::ParseVector(pixelSpacing, tmp) &&
|
|
720 pixelSpacing.size() == 2)
|
|
721 {
|
|
722 SetPixelSpacing(pixelSpacing[0], pixelSpacing[1]);
|
|
723 }
|
|
724
|
|
725 //SetPan(-0.5 * GetPixelSpacingX(), -0.5 * GetPixelSpacingY());
|
|
726
|
|
727 OrthancPlugins::DicomDatasetReader reader(dataset);
|
|
728
|
|
729 unsigned int width, height;
|
|
730 if (!reader.GetUnsignedIntegerValue(width, ConvertTag(Orthanc::DICOM_TAG_COLUMNS)) ||
|
|
731 !reader.GetUnsignedIntegerValue(height, ConvertTag(Orthanc::DICOM_TAG_ROWS)))
|
|
732 {
|
|
733 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
|
|
734 }
|
|
735 else
|
|
736 {
|
|
737 SetSize(width, height);
|
|
738 }
|
|
739 }
|
|
740
|
|
741
|
|
742 void SetSourceImage(Orthanc::ImageAccessor* image) // Takes ownership
|
|
743 {
|
|
744 std::auto_ptr<Orthanc::ImageAccessor> raii(image);
|
|
745
|
|
746 if (image == NULL)
|
|
747 {
|
|
748 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
|
|
749 }
|
|
750
|
|
751 SetSize(image->GetWidth(), image->GetHeight());
|
|
752
|
|
753 source_ = raii;
|
|
754 ApplyConverter();
|
|
755 }
|
|
756
|
|
757
|
|
758 virtual void Render(Orthanc::ImageAccessor& buffer,
|
|
759 const Matrix& viewTransform,
|
|
760 ImageInterpolation interpolation) const
|
|
761 {
|
|
762 if (converted_.get() != NULL)
|
|
763 {
|
|
764 if (converted_->GetFormat() != Orthanc::PixelFormat_Float32)
|
|
765 {
|
|
766 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
|
|
767 }
|
|
768
|
|
769 unsigned int cropX, cropY, cropWidth, cropHeight;
|
|
770 GetCrop(cropX, cropY, cropWidth, cropHeight);
|
|
771
|
|
772 Matrix m = LinearAlgebra::Product(viewTransform,
|
|
773 GetTransform(),
|
|
774 CreateOffsetMatrix(cropX, cropY));
|
|
775
|
|
776 Orthanc::ImageAccessor cropped;
|
|
777 converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
|
|
778
|
|
779 ApplyProjectiveTransform(buffer, cropped, m, interpolation, false);
|
|
780 }
|
|
781 }
|
|
782
|
|
783
|
|
784 virtual bool GetDefaultWindowing(float& center,
|
|
785 float& width) const
|
|
786 {
|
|
787 if (converter_.get() != NULL &&
|
|
788 converter_->HasDefaultWindow())
|
|
789 {
|
|
790 center = static_cast<float>(converter_->GetDefaultWindowCenter());
|
|
791 width = static_cast<float>(converter_->GetDefaultWindowWidth());
|
|
792 return true;
|
|
793 }
|
|
794 else
|
|
795 {
|
|
796 return false;
|
|
797 }
|
|
798 }
|
|
799
|
|
800
|
|
801 virtual bool GetRange(float& minValue,
|
|
802 float& maxValue) const
|
|
803 {
|
|
804 if (converted_.get() != NULL)
|
|
805 {
|
|
806 if (converted_->GetFormat() != Orthanc::PixelFormat_Float32)
|
|
807 {
|
|
808 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
|
|
809 }
|
|
810
|
|
811 Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, *converted_);
|
|
812 return true;
|
|
813 }
|
|
814 else
|
|
815 {
|
|
816 return false;
|
|
817 }
|
|
818 }
|
|
819 };
|
|
820
|
|
821
|
|
822 RadiographyScene::Layer& RadiographyScene::RegisterLayer(RadiographyScene::Layer* layer)
|
|
823 {
|
|
824 if (layer == NULL)
|
|
825 {
|
|
826 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
|
|
827 }
|
|
828
|
|
829 std::auto_ptr<Layer> raii(layer);
|
|
830
|
|
831 size_t index = countLayers_++;
|
|
832 raii->SetIndex(index);
|
|
833 layers_[index] = raii.release();
|
|
834
|
|
835 EmitMessage(GeometryChangedMessage(*this));
|
|
836 EmitMessage(ContentChangedMessage(*this));
|
|
837
|
|
838 return *layer;
|
|
839 }
|
|
840
|
|
841
|
|
842 RadiographyScene::RadiographyScene(MessageBroker& broker,
|
|
843 OrthancApiClient& orthanc) :
|
|
844 IObserver(broker),
|
|
845 IObservable(broker),
|
|
846 orthanc_(orthanc),
|
|
847 countLayers_(0),
|
|
848 hasWindowing_(false),
|
|
849 windowingCenter_(0), // Dummy initialization
|
|
850 windowingWidth_(0) // Dummy initialization
|
|
851 {
|
|
852 }
|
|
853
|
|
854
|
|
855 RadiographyScene::~RadiographyScene()
|
|
856 {
|
|
857 for (Layers::iterator it = layers_.begin(); it != layers_.end(); it++)
|
|
858 {
|
|
859 assert(it->second != NULL);
|
|
860 delete it->second;
|
|
861 }
|
|
862 }
|
|
863
|
|
864
|
|
865 bool RadiographyScene::GetWindowing(float& center,
|
|
866 float& width) const
|
|
867 {
|
|
868 if (hasWindowing_)
|
|
869 {
|
|
870 center = windowingCenter_;
|
|
871 width = windowingWidth_;
|
|
872 return true;
|
|
873 }
|
|
874 else
|
|
875 {
|
|
876 return false;
|
|
877 }
|
|
878 }
|
|
879
|
|
880
|
|
881 void RadiographyScene::GetWindowingWithDefault(float& center,
|
|
882 float& width) const
|
|
883 {
|
|
884 if (!GetWindowing(center, width))
|
|
885 {
|
|
886 center = 128;
|
|
887 width = 256;
|
|
888 }
|
|
889 }
|
|
890
|
|
891
|
|
892 void RadiographyScene::SetWindowing(float center,
|
|
893 float width)
|
|
894 {
|
|
895 hasWindowing_ = true;
|
|
896 windowingCenter_ = center;
|
|
897 windowingWidth_ = width;
|
|
898 }
|
|
899
|
|
900
|
|
901 RadiographyScene::Layer& RadiographyScene::LoadText(const Orthanc::Font& font,
|
|
902 const std::string& utf8)
|
|
903 {
|
|
904 std::auto_ptr<AlphaLayer> alpha(new AlphaLayer(*this));
|
|
905 alpha->LoadText(font, utf8);
|
|
906
|
|
907 return RegisterLayer(alpha.release());
|
|
908 }
|
|
909
|
|
910
|
|
911 RadiographyScene::Layer& RadiographyScene::LoadTestBlock(unsigned int width,
|
|
912 unsigned int height)
|
|
913 {
|
|
914 std::auto_ptr<Orthanc::Image> block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false));
|
|
915
|
|
916 for (unsigned int padding = 0;
|
|
917 (width > 2 * padding) && (height > 2 * padding);
|
|
918 padding++)
|
|
919 {
|
|
920 uint8_t color;
|
|
921 if (255 > 10 * padding)
|
|
922 {
|
|
923 color = 255 - 10 * padding;
|
|
924 }
|
|
925 else
|
|
926 {
|
|
927 color = 0;
|
|
928 }
|
|
929
|
|
930 Orthanc::ImageAccessor region;
|
|
931 block->GetRegion(region, padding, padding, width - 2 * padding, height - 2 * padding);
|
|
932 Orthanc::ImageProcessing::Set(region, color);
|
|
933 }
|
|
934
|
|
935 std::auto_ptr<AlphaLayer> alpha(new AlphaLayer(*this));
|
|
936 alpha->SetAlpha(block.release());
|
|
937
|
|
938 return RegisterLayer(alpha.release());
|
|
939 }
|
|
940
|
|
941
|
|
942 RadiographyScene::Layer& RadiographyScene::LoadDicomFrame(const std::string& instance,
|
|
943 unsigned int frame,
|
|
944 bool httpCompression)
|
|
945 {
|
|
946 Layer& layer = RegisterLayer(new DicomLayer);
|
|
947
|
|
948 {
|
|
949 IWebService::Headers headers;
|
|
950 std::string uri = "/instances/" + instance + "/tags";
|
|
951
|
|
952 orthanc_.GetBinaryAsync(
|
|
953 uri, headers,
|
|
954 new Callable<RadiographyScene, OrthancApiClient::BinaryResponseReadyMessage>
|
|
955 (*this, &RadiographyScene::OnTagsReceived), NULL,
|
|
956 new Orthanc::SingleValueObject<size_t>(layer.GetIndex()));
|
|
957 }
|
|
958
|
|
959 {
|
|
960 IWebService::Headers headers;
|
|
961 headers["Accept"] = "image/x-portable-arbitrarymap";
|
|
962
|
|
963 if (httpCompression)
|
|
964 {
|
|
965 headers["Accept-Encoding"] = "gzip";
|
|
966 }
|
|
967
|
|
968 std::string uri = ("/instances/" + instance + "/frames/" +
|
|
969 boost::lexical_cast<std::string>(frame) + "/image-uint16");
|
|
970
|
|
971 orthanc_.GetBinaryAsync(
|
|
972 uri, headers,
|
|
973 new Callable<RadiographyScene, OrthancApiClient::BinaryResponseReadyMessage>
|
|
974 (*this, &RadiographyScene::OnFrameReceived), NULL,
|
|
975 new Orthanc::SingleValueObject<size_t>(layer.GetIndex()));
|
|
976 }
|
|
977
|
|
978 return layer;
|
|
979 }
|
|
980
|
|
981
|
|
982 void RadiographyScene::OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message)
|
|
983 {
|
|
984 size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&>
|
|
985 (message.GetPayload()).GetValue();
|
|
986
|
|
987 LOG(INFO) << "JSON received: " << message.GetUri().c_str()
|
|
988 << " (" << message.GetAnswerSize() << " bytes) for layer " << index;
|
|
989
|
|
990 Layers::iterator layer = layers_.find(index);
|
|
991 if (layer != layers_.end())
|
|
992 {
|
|
993 assert(layer->second != NULL);
|
|
994
|
|
995 OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer(), message.GetAnswerSize());
|
|
996 dynamic_cast<DicomLayer*>(layer->second)->SetDicomTags(dicom);
|
|
997
|
|
998 float c, w;
|
|
999 if (!hasWindowing_ &&
|
|
1000 layer->second->GetDefaultWindowing(c, w))
|
|
1001 {
|
|
1002 hasWindowing_ = true;
|
|
1003 windowingCenter_ = c;
|
|
1004 windowingWidth_ = w;
|
|
1005 }
|
|
1006
|
|
1007 EmitMessage(GeometryChangedMessage(*this));
|
|
1008 }
|
|
1009 }
|
|
1010
|
|
1011
|
|
1012 void RadiographyScene::OnFrameReceived(const OrthancApiClient::BinaryResponseReadyMessage& message)
|
|
1013 {
|
|
1014 size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&>(message.GetPayload()).GetValue();
|
|
1015
|
|
1016 LOG(INFO) << "DICOM frame received: " << message.GetUri().c_str()
|
|
1017 << " (" << message.GetAnswerSize() << " bytes) for layer " << index;
|
|
1018
|
|
1019 Layers::iterator layer = layers_.find(index);
|
|
1020 if (layer != layers_.end())
|
|
1021 {
|
|
1022 assert(layer->second != NULL);
|
|
1023
|
|
1024 std::string content;
|
|
1025 if (message.GetAnswerSize() > 0)
|
|
1026 {
|
|
1027 content.assign(reinterpret_cast<const char*>(message.GetAnswer()), message.GetAnswerSize());
|
|
1028 }
|
|
1029
|
|
1030 std::auto_ptr<Orthanc::PamReader> reader(new Orthanc::PamReader);
|
|
1031 reader->ReadFromMemory(content);
|
|
1032 dynamic_cast<DicomLayer*>(layer->second)->SetSourceImage(reader.release());
|
|
1033
|
|
1034 EmitMessage(ContentChangedMessage(*this));
|
|
1035 }
|
|
1036 }
|
|
1037
|
|
1038
|
|
1039 Extent2D RadiographyScene::GetSceneExtent() const
|
|
1040 {
|
|
1041 Extent2D extent;
|
|
1042
|
|
1043 for (Layers::const_iterator it = layers_.begin();
|
|
1044 it != layers_.end(); ++it)
|
|
1045 {
|
|
1046 assert(it->second != NULL);
|
|
1047 extent.Union(it->second->GetExtent());
|
|
1048 }
|
|
1049
|
|
1050 return extent;
|
|
1051 }
|
|
1052
|
|
1053
|
|
1054 void RadiographyScene::Render(Orthanc::ImageAccessor& buffer,
|
|
1055 const Matrix& viewTransform,
|
|
1056 ImageInterpolation interpolation) const
|
|
1057 {
|
|
1058 Orthanc::ImageProcessing::Set(buffer, 0);
|
|
1059
|
|
1060 // Render layers in the background-to-foreground order
|
|
1061 for (size_t index = 0; index < countLayers_; index++)
|
|
1062 {
|
|
1063 Layers::const_iterator it = layers_.find(index);
|
|
1064 if (it != layers_.end())
|
|
1065 {
|
|
1066 assert(it->second != NULL);
|
|
1067 it->second->Render(buffer, viewTransform, interpolation);
|
|
1068 }
|
|
1069 }
|
|
1070 }
|
|
1071
|
|
1072
|
|
1073 bool RadiographyScene::LookupLayer(size_t& index /* out */,
|
|
1074 double x,
|
|
1075 double y) const
|
|
1076 {
|
|
1077 // Render layers in the foreground-to-background order
|
|
1078 for (size_t i = countLayers_; i > 0; i--)
|
|
1079 {
|
|
1080 index = i - 1;
|
|
1081 Layers::const_iterator it = layers_.find(index);
|
|
1082 if (it != layers_.end())
|
|
1083 {
|
|
1084 assert(it->second != NULL);
|
|
1085 if (it->second->Contains(x, y))
|
|
1086 {
|
|
1087 return true;
|
|
1088 }
|
|
1089 }
|
|
1090 }
|
|
1091
|
|
1092 return false;
|
|
1093 }
|
|
1094
|
|
1095
|
|
1096 void RadiographyScene::DrawBorder(CairoContext& context,
|
|
1097 unsigned int layer,
|
|
1098 double zoom)
|
|
1099 {
|
|
1100 Layers::const_iterator found = layers_.find(layer);
|
|
1101
|
|
1102 if (found != layers_.end())
|
|
1103 {
|
|
1104 context.SetSourceColor(255, 0, 0);
|
|
1105 found->second->DrawBorders(context, zoom);
|
|
1106 }
|
|
1107 }
|
|
1108
|
|
1109
|
|
1110 void RadiographyScene::GetRange(float& minValue,
|
|
1111 float& maxValue) const
|
|
1112 {
|
|
1113 bool first = true;
|
|
1114
|
|
1115 for (Layers::const_iterator it = layers_.begin();
|
|
1116 it != layers_.end(); it++)
|
|
1117 {
|
|
1118 assert(it->second != NULL);
|
|
1119
|
|
1120 float a, b;
|
|
1121 if (it->second->GetRange(a, b))
|
|
1122 {
|
|
1123 if (first)
|
|
1124 {
|
|
1125 minValue = a;
|
|
1126 maxValue = b;
|
|
1127 first = false;
|
|
1128 }
|
|
1129 else
|
|
1130 {
|
|
1131 minValue = std::min(a, minValue);
|
|
1132 maxValue = std::max(b, maxValue);
|
|
1133 }
|
|
1134 }
|
|
1135 }
|
|
1136
|
|
1137 if (first)
|
|
1138 {
|
|
1139 minValue = 0;
|
|
1140 maxValue = 0;
|
|
1141 }
|
|
1142 }
|
|
1143
|
|
1144
|
|
1145 // Export using PAM is faster than using PNG, but requires Orthanc
|
|
1146 // core >= 1.4.3
|
|
1147 void RadiographyScene::ExportDicom(const Orthanc::DicomMap& dicom,
|
|
1148 double pixelSpacingX,
|
|
1149 double pixelSpacingY,
|
|
1150 bool invert,
|
|
1151 ImageInterpolation interpolation,
|
|
1152 bool usePam)
|
|
1153 {
|
|
1154 if (pixelSpacingX <= 0 ||
|
|
1155 pixelSpacingY <= 0)
|
|
1156 {
|
|
1157 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
|
|
1158 }
|
|
1159
|
|
1160 LOG(INFO) << "Exporting DICOM";
|
|
1161
|
|
1162 Extent2D extent = GetSceneExtent();
|
|
1163
|
|
1164 int w = std::ceil(extent.GetWidth() / pixelSpacingX);
|
|
1165 int h = std::ceil(extent.GetHeight() / pixelSpacingY);
|
|
1166
|
|
1167 if (w < 0 || h < 0)
|
|
1168 {
|
|
1169 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
|
|
1170 }
|
|
1171
|
|
1172 Orthanc::Image layers(Orthanc::PixelFormat_Float32,
|
|
1173 static_cast<unsigned int>(w),
|
|
1174 static_cast<unsigned int>(h), false);
|
|
1175
|
|
1176 Matrix view = LinearAlgebra::Product(
|
|
1177 CreateScalingMatrix(1.0 / pixelSpacingX, 1.0 / pixelSpacingY),
|
|
1178 CreateOffsetMatrix(-extent.GetX1(), -extent.GetY1()));
|
|
1179
|
|
1180 Render(layers, view, interpolation);
|
|
1181
|
|
1182 Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16,
|
|
1183 layers.GetWidth(), layers.GetHeight(), false);
|
|
1184 Orthanc::ImageProcessing::Convert(rendered, layers);
|
|
1185
|
|
1186 std::string base64;
|
|
1187
|
|
1188 {
|
|
1189 std::string content;
|
|
1190
|
|
1191 if (usePam)
|
|
1192 {
|
|
1193 Orthanc::PamWriter writer;
|
|
1194 writer.WriteToMemory(content, rendered);
|
|
1195 }
|
|
1196 else
|
|
1197 {
|
|
1198 Orthanc::PngWriter writer;
|
|
1199 writer.WriteToMemory(content, rendered);
|
|
1200 }
|
|
1201
|
|
1202 Orthanc::Toolbox::EncodeBase64(base64, content);
|
|
1203 }
|
|
1204
|
|
1205 std::set<Orthanc::DicomTag> tags;
|
|
1206 dicom.GetTags(tags);
|
|
1207
|
|
1208 Json::Value json = Json::objectValue;
|
|
1209 json["Tags"] = Json::objectValue;
|
|
1210
|
|
1211 for (std::set<Orthanc::DicomTag>::const_iterator
|
|
1212 tag = tags.begin(); tag != tags.end(); ++tag)
|
|
1213 {
|
|
1214 const Orthanc::DicomValue& value = dicom.GetValue(*tag);
|
|
1215 if (!value.IsNull() &&
|
|
1216 !value.IsBinary())
|
|
1217 {
|
|
1218 json["Tags"][tag->Format()] = value.GetContent();
|
|
1219 }
|
|
1220 }
|
|
1221
|
|
1222 json["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] =
|
|
1223 (invert ? "MONOCHROME1" : "MONOCHROME2");
|
|
1224
|
|
1225 // WARNING: The order of PixelSpacing is Y/X. We use "%0.8f" to
|
|
1226 // avoid floating-point numbers to grow over 16 characters,
|
|
1227 // which would be invalid according to DICOM standard
|
|
1228 // ("dciodvfy" would complain).
|
|
1229 char buf[32];
|
|
1230 sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX);
|
|
1231
|
|
1232 json["Tags"][Orthanc::DICOM_TAG_PIXEL_SPACING.Format()] = buf;
|
|
1233
|
|
1234 float center, width;
|
|
1235 if (GetWindowing(center, width))
|
|
1236 {
|
|
1237 json["Tags"][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()] =
|
|
1238 boost::lexical_cast<std::string>(boost::math::iround(center));
|
|
1239
|
|
1240 json["Tags"][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()] =
|
|
1241 boost::lexical_cast<std::string>(boost::math::iround(width));
|
|
1242 }
|
|
1243
|
|
1244 // This is Data URI scheme: https://en.wikipedia.org/wiki/Data_URI_scheme
|
|
1245 json["Content"] = ("data:" +
|
|
1246 std::string(usePam ? Orthanc::MIME_PAM : Orthanc::MIME_PNG) +
|
|
1247 ";base64," + base64);
|
|
1248
|
|
1249 orthanc_.PostJsonAsyncExpectJson(
|
|
1250 "/tools/create-dicom", json,
|
|
1251 new Callable<RadiographyScene, OrthancApiClient::JsonResponseReadyMessage>
|
|
1252 (*this, &RadiographyScene::OnDicomExported),
|
|
1253 NULL, NULL);
|
|
1254 }
|
|
1255
|
|
1256
|
|
1257 void RadiographyScene::OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message)
|
|
1258 {
|
|
1259 LOG(INFO) << "DICOM export was successful:"
|
|
1260 << message.GetJson().toStyledString();
|
|
1261 }
|
|
1262 }
|