comparison Applications/Samples/SingleFrameEditorApplication.h @ 384:d20d75f20c5d

better following of the MVC design pattern
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 07 Nov 2018 16:17:02 +0100
parents 8eb4fe74000f
children d87fe075d31b
comparison
equal deleted inserted replaced
383:939f626eb6d7 384:d20d75f20c5d
216 ApplyTransform(x, y, transform_); 216 ApplyTransform(x, y, transform_);
217 } 217 }
218 218
219 219
220 public: 220 public:
221 Bitmap(size_t index) : 221 Bitmap() :
222 index_(index), 222 index_(0),
223 hasSize_(false), 223 hasSize_(false),
224 width_(0), 224 width_(0),
225 height_(0), 225 height_(0),
226 hasCrop_(false), 226 hasCrop_(false),
227 pixelSpacingX_(1), 227 pixelSpacingX_(1),
236 236
237 virtual ~Bitmap() 237 virtual ~Bitmap()
238 { 238 {
239 } 239 }
240 240
241 void SetIndex(size_t index)
242 {
243 index_ = index;
244 }
245
241 size_t GetIndex() const 246 size_t GetIndex() const
242 { 247 {
243 return index_; 248 return index_;
244 } 249 }
245 250
701 std::auto_ptr<Orthanc::ImageAccessor> alpha_; // Grayscale8 706 std::auto_ptr<Orthanc::ImageAccessor> alpha_; // Grayscale8
702 bool useWindowing_; 707 bool useWindowing_;
703 float foreground_; 708 float foreground_;
704 709
705 public: 710 public:
706 AlphaBitmap(size_t index, 711 AlphaBitmap(const BitmapStack& stack) :
707 const BitmapStack& stack) :
708 Bitmap(index),
709 stack_(stack), 712 stack_(stack),
710 useWindowing_(true), 713 useWindowing_(true),
711 foreground_(0) 714 foreground_(0)
712 { 715 {
713 } 716 }
748 751
749 virtual void Render(Orthanc::ImageAccessor& buffer, 752 virtual void Render(Orthanc::ImageAccessor& buffer,
750 const Matrix& viewTransform, 753 const Matrix& viewTransform,
751 ImageInterpolation interpolation) const 754 ImageInterpolation interpolation) const
752 { 755 {
756 if (alpha_.get() == NULL)
757 {
758 return;
759 }
760
753 if (buffer.GetFormat() != Orthanc::PixelFormat_Float32) 761 if (buffer.GetFormat() != Orthanc::PixelFormat_Float32)
754 { 762 {
755 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); 763 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
756 } 764 }
757 765
848 converted_.reset(converter_->ConvertFrame(*source_)); 856 converted_.reset(converter_->ConvertFrame(*source_));
849 } 857 }
850 } 858 }
851 859
852 public: 860 public:
853 DicomBitmap(size_t index) :
854 Bitmap(index)
855 {
856 }
857
858 void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset) 861 void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset)
859 { 862 {
860 converter_.reset(new DicomFrameConverter); 863 converter_.reset(new DicomFrameConverter);
861 converter_->ReadParameters(dataset); 864 converter_->ReadParameters(dataset);
862 ApplyConverter(); 865 ApplyConverter();
976 size_t countBitmaps_; 979 size_t countBitmaps_;
977 bool hasWindowing_; 980 bool hasWindowing_;
978 float windowingCenter_; 981 float windowingCenter_;
979 float windowingWidth_; 982 float windowingWidth_;
980 Bitmaps bitmaps_; 983 Bitmaps bitmaps_;
981 bool hasSelection_;
982 size_t selectedBitmap_;
983 984
984 public: 985 public:
985 BitmapStack(MessageBroker& broker, 986 BitmapStack(MessageBroker& broker,
986 OrthancApiClient& orthanc) : 987 OrthancApiClient& orthanc) :
987 IObserver(broker), 988 IObserver(broker),
988 IObservable(broker), 989 IObservable(broker),
989 orthanc_(orthanc), 990 orthanc_(orthanc),
990 countBitmaps_(0), 991 countBitmaps_(0),
991 hasWindowing_(false), 992 hasWindowing_(false),
992 windowingCenter_(0), // Dummy initialization 993 windowingCenter_(0), // Dummy initialization
993 windowingWidth_(0), // Dummy initialization 994 windowingWidth_(0) // Dummy initialization
994 hasSelection_(false), 995 {
995 selectedBitmap_(0) // Dummy initialization 996 }
996 { 997
997 } 998
998
999
1000 void Unselect()
1001 {
1002 hasSelection_ = false;
1003 }
1004
1005
1006 void Select(size_t bitmap)
1007 {
1008 hasSelection_ = true;
1009 selectedBitmap_ = bitmap;
1010 }
1011
1012
1013 bool GetSelectedBitmap(size_t& bitmap) const
1014 {
1015 if (hasSelection_)
1016 {
1017 bitmap = selectedBitmap_;
1018 return true;
1019 }
1020 else
1021 {
1022 return false;
1023 }
1024 }
1025
1026
1027 virtual ~BitmapStack() 999 virtual ~BitmapStack()
1028 { 1000 {
1029 for (Bitmaps::iterator it = bitmaps_.begin(); it != bitmaps_.end(); it++) 1001 for (Bitmaps::iterator it = bitmaps_.begin(); it != bitmaps_.end(); it++)
1030 { 1002 {
1031 assert(it->second != NULL); 1003 assert(it->second != NULL);
1066 1038
1067 { 1039 {
1068 hasWindowing_ = true; 1040 hasWindowing_ = true;
1069 windowingCenter_ = center; 1041 windowingCenter_ = center;
1070 windowingWidth_ = width; 1042 windowingWidth_ = width;
1071 1043 }
1072 //EmitMessage(ContentChangedMessage(*this)); 1044
1045
1046 Bitmap& RegisterBitmap(Bitmap* bitmap)
1047 {
1048 if (bitmap == NULL)
1049 {
1050 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
1051 }
1052
1053 std::auto_ptr<Bitmap> raii(bitmap);
1054
1055 size_t index = countBitmaps_++;
1056 raii->SetIndex(index);
1057 bitmaps_[index] = raii.release();
1058
1059 EmitMessage(GeometryChangedMessage(*this));
1060 EmitMessage(ContentChangedMessage(*this));
1061
1062 return *bitmap;
1073 } 1063 }
1074 1064
1075 1065
1076 Bitmap& LoadText(const Orthanc::Font& font, 1066 Bitmap& LoadText(const Orthanc::Font& font,
1077 const std::string& utf8) 1067 const std::string& utf8)
1078 { 1068 {
1079 size_t bitmap = countBitmaps_++; 1069 std::auto_ptr<AlphaBitmap> alpha(new AlphaBitmap(*this));
1080
1081 std::auto_ptr<AlphaBitmap> alpha(new AlphaBitmap(bitmap, *this));
1082 alpha->LoadText(font, utf8); 1070 alpha->LoadText(font, utf8);
1083 1071
1084 AlphaBitmap* ptr = alpha.get(); 1072 return RegisterBitmap(alpha.release());
1085 bitmaps_[bitmap] = alpha.release();
1086
1087 return *ptr;
1088 } 1073 }
1089 1074
1090 1075
1091 Bitmap& LoadTestBlock(unsigned int width, 1076 Bitmap& LoadTestBlock(unsigned int width,
1092 unsigned int height) 1077 unsigned int height)
1093 { 1078 {
1094 size_t bitmap = countBitmaps_++;
1095
1096 std::auto_ptr<AlphaBitmap> alpha(new AlphaBitmap(bitmap, *this));
1097
1098 std::auto_ptr<Orthanc::Image> block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false)); 1079 std::auto_ptr<Orthanc::Image> block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false));
1099 1080
1100 for (unsigned int padding = 0; 1081 for (unsigned int padding = 0;
1101 (width > 2 * padding) && (height > 2 * padding); 1082 (width > 2 * padding) && (height > 2 * padding);
1102 padding++) 1083 padding++)
1114 Orthanc::ImageAccessor region; 1095 Orthanc::ImageAccessor region;
1115 block->GetRegion(region, padding, padding, width - 2 * padding, height - 2 * padding); 1096 block->GetRegion(region, padding, padding, width - 2 * padding, height - 2 * padding);
1116 Orthanc::ImageProcessing::Set(region, color); 1097 Orthanc::ImageProcessing::Set(region, color);
1117 } 1098 }
1118 1099
1100 std::auto_ptr<AlphaBitmap> alpha(new AlphaBitmap(*this));
1119 alpha->SetAlpha(block.release()); 1101 alpha->SetAlpha(block.release());
1120 1102
1121 AlphaBitmap* ptr = alpha.get(); 1103 return RegisterBitmap(alpha.release());
1122 bitmaps_[bitmap] = alpha.release();
1123
1124 return *ptr;
1125 } 1104 }
1126 1105
1127 1106
1128 Bitmap& LoadFrame(const std::string& instance, 1107 Bitmap& LoadFrame(const std::string& instance,
1129 unsigned int frame, 1108 unsigned int frame,
1130 bool httpCompression) 1109 bool httpCompression)
1131 { 1110 {
1132 size_t bitmap = countBitmaps_++; 1111 Bitmap& bitmap = RegisterBitmap(new DicomBitmap);
1133 1112
1134 bitmaps_[bitmap] = new DicomBitmap(bitmap);
1135
1136 { 1113 {
1137 IWebService::Headers headers; 1114 IWebService::Headers headers;
1138 std::string uri = "/instances/" + instance + "/tags"; 1115 std::string uri = "/instances/" + instance + "/tags";
1139 orthanc_.GetBinaryAsync(uri, headers, 1116 orthanc_.GetBinaryAsync(uri, headers,
1140 new Callable<BitmapStack, OrthancApiClient::BinaryResponseReadyMessage> 1117 new Callable<BitmapStack, OrthancApiClient::BinaryResponseReadyMessage>
1141 (*this, &BitmapStack::OnTagsReceived), NULL, 1118 (*this, &BitmapStack::OnTagsReceived), NULL,
1142 new Orthanc::SingleValueObject<size_t>(bitmap)); 1119 new Orthanc::SingleValueObject<size_t>(bitmap.GetIndex()));
1143 } 1120 }
1144 1121
1145 { 1122 {
1146 IWebService::Headers headers; 1123 IWebService::Headers headers;
1147 headers["Accept"] = "image/x-portable-arbitrarymap"; 1124 headers["Accept"] = "image/x-portable-arbitrarymap";
1153 1130
1154 std::string uri = "/instances/" + instance + "/frames/" + boost::lexical_cast<std::string>(frame) + "/image-uint16"; 1131 std::string uri = "/instances/" + instance + "/frames/" + boost::lexical_cast<std::string>(frame) + "/image-uint16";
1155 orthanc_.GetBinaryAsync(uri, headers, 1132 orthanc_.GetBinaryAsync(uri, headers,
1156 new Callable<BitmapStack, OrthancApiClient::BinaryResponseReadyMessage> 1133 new Callable<BitmapStack, OrthancApiClient::BinaryResponseReadyMessage>
1157 (*this, &BitmapStack::OnFrameReceived), NULL, 1134 (*this, &BitmapStack::OnFrameReceived), NULL,
1158 new Orthanc::SingleValueObject<size_t>(bitmap)); 1135 new Orthanc::SingleValueObject<size_t>(bitmap.GetIndex()));
1159 } 1136 }
1160 1137
1161 return *bitmaps_[bitmap]; 1138 return bitmap;
1162 } 1139 }
1163 1140
1164 1141
1165 void OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) 1142 void OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message)
1166 { 1143 {
1272 } 1249 }
1273 1250
1274 return false; 1251 return false;
1275 } 1252 }
1276 1253
1277 void DrawControls(CairoContext& context, 1254
1278 double zoom) 1255 void DrawBorder(CairoContext& context,
1279 { 1256 unsigned int bitmap,
1280 if (hasSelection_) 1257 double zoom)
1281 { 1258 {
1282 Bitmaps::const_iterator bitmap = bitmaps_.find(selectedBitmap_); 1259 Bitmaps::const_iterator found = bitmaps_.find(bitmap);
1283 1260
1284 if (bitmap != bitmaps_.end()) 1261 if (found != bitmaps_.end())
1285 { 1262 {
1286 context.SetSourceColor(255, 0, 0); 1263 context.SetSourceColor(255, 0, 0);
1287 //view.ApplyTransform(context); 1264 found->second->DrawBorders(context, zoom);
1288 bitmap->second->DrawBorders(context, zoom);
1289 }
1290 } 1265 }
1291 } 1266 }
1292 1267
1293 1268
1294 void GetRange(float& minValue, 1269 void GetRange(float& minValue,
1321 if (first) 1296 if (first)
1322 { 1297 {
1323 minValue = 0; 1298 minValue = 0;
1324 maxValue = 0; 1299 maxValue = 0;
1325 } 1300 }
1301 }
1302
1303
1304 void Export(const Orthanc::DicomMap& dicom,
1305 double pixelSpacingX,
1306 double pixelSpacingY,
1307 bool invert,
1308 ImageInterpolation interpolation)
1309 {
1310 if (pixelSpacingX <= 0 ||
1311 pixelSpacingY <= 0)
1312 {
1313 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
1314 }
1315
1316 LOG(WARNING) << "Exporting DICOM";
1317
1318 Extent2D extent = GetSceneExtent();
1319
1320 int w = std::ceil(extent.GetWidth() / pixelSpacingX);
1321 int h = std::ceil(extent.GetHeight() / pixelSpacingY);
1322
1323 if (w < 0 || h < 0)
1324 {
1325 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1326 }
1327
1328 Orthanc::Image layers(Orthanc::PixelFormat_Float32,
1329 static_cast<unsigned int>(w),
1330 static_cast<unsigned int>(h), false);
1331
1332 Matrix view = LinearAlgebra::Product(
1333 CreateScalingMatrix(1.0 / pixelSpacingX, 1.0 / pixelSpacingY),
1334 CreateOffsetMatrix(-extent.GetX1(), -extent.GetY1()));
1335
1336 Render(layers, view, interpolation);
1337
1338 Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16,
1339 layers.GetWidth(), layers.GetHeight(), false);
1340 Orthanc::ImageProcessing::Convert(rendered, layers);
1341
1342 std::string base64;
1343
1344 {
1345 std::string content;
1346
1347 #if EXPORT_USING_PAM == 1
1348 {
1349 Orthanc::PamWriter writer;
1350 writer.WriteToMemory(content, rendered);
1351 }
1352 #else
1353 {
1354 Orthanc::PngWriter writer;
1355 writer.WriteToMemory(content, rendered);
1356 }
1357 #endif
1358
1359 Orthanc::Toolbox::EncodeBase64(base64, content);
1360 }
1361
1362 std::set<Orthanc::DicomTag> tags;
1363 dicom.GetTags(tags);
1364
1365 Json::Value json = Json::objectValue;
1366 json["Tags"] = Json::objectValue;
1367
1368 for (std::set<Orthanc::DicomTag>::const_iterator
1369 tag = tags.begin(); tag != tags.end(); ++tag)
1370 {
1371 const Orthanc::DicomValue& value = dicom.GetValue(*tag);
1372 if (!value.IsNull() &&
1373 !value.IsBinary())
1374 {
1375 json["Tags"][tag->Format()] = value.GetContent();
1376 }
1377 }
1378
1379 json["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] =
1380 (invert ? "MONOCHROME1" : "MONOCHROME2");
1381
1382 // WARNING: The order of PixelSpacing is Y/X
1383 char buf[32];
1384 sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX);
1385
1386 json["Tags"][Orthanc::DICOM_TAG_PIXEL_SPACING.Format()] = buf;
1387
1388 float center, width;
1389 if (GetWindowing(center, width))
1390 {
1391 json["Tags"][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()] =
1392 boost::lexical_cast<std::string>(boost::math::iround(center));
1393
1394 json["Tags"][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()] =
1395 boost::lexical_cast<std::string>(boost::math::iround(width));
1396 }
1397
1398 #if EXPORT_USING_PAM == 1
1399 json["Content"] = "data:" + std::string(Orthanc::MIME_PAM) + ";base64," + base64;
1400 #else
1401 json["Content"] = "data:" + std::string(Orthanc::MIME_PNG) + ";base64," + base64;
1402 #endif
1403
1404 orthanc_.PostJsonAsyncExpectJson(
1405 "/tools/create-dicom", json,
1406 new Callable<BitmapStack, OrthancApiClient::JsonResponseReadyMessage>
1407 (*this, &BitmapStack::OnDicomExported),
1408 NULL, NULL);
1409 }
1410
1411
1412 void OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message)
1413 {
1414 LOG(WARNING) << "DICOM export was successful:"
1415 << message.GetJson().toStyledString();
1326 } 1416 }
1327 }; 1417 };
1328 1418
1329 1419
1330 class UndoRedoStack : public boost::noncopyable 1420 class UndoRedoStack : public boost::noncopyable
2205 }; 2295 };
2206 2296
2207 2297
2208 class BitmapStackWidget : 2298 class BitmapStackWidget :
2209 public WorldSceneWidget, 2299 public WorldSceneWidget,
2210 public IObservable,
2211 public IObserver 2300 public IObserver
2212 { 2301 {
2213 private: 2302 private:
2214 BitmapStack& stack_; 2303 BitmapStack& stack_;
2215 std::auto_ptr<Orthanc::Image> floatBuffer_; 2304 std::auto_ptr<Orthanc::Image> floatBuffer_;
2216 std::auto_ptr<CairoSurface> cairoBuffer_; 2305 std::auto_ptr<CairoSurface> cairoBuffer_;
2217 bool invert_; 2306 bool invert_;
2218 ImageInterpolation interpolation_; 2307 ImageInterpolation interpolation_;
2308 bool hasSelection_;
2309 size_t selectedBitmap_;
2219 2310
2220 virtual bool RenderInternal(unsigned int width, 2311 virtual bool RenderInternal(unsigned int width,
2221 unsigned int height, 2312 unsigned int height,
2222 ImageInterpolation interpolation) 2313 ImageInterpolation interpolation)
2223 { 2314 {
2293 2384
2294 return true; 2385 return true;
2295 } 2386 }
2296 } 2387 }
2297 2388
2298
2299 protected: 2389 protected:
2300 virtual Extent2D GetSceneExtent() 2390 virtual Extent2D GetSceneExtent()
2301 { 2391 {
2302 return stack_.GetSceneExtent(); 2392 return stack_.GetSceneExtent();
2303 } 2393 }
2321 // https://www.cairographics.org/FAQ/#clear_a_surface 2411 // https://www.cairographics.org/FAQ/#clear_a_surface
2322 context.SetSourceColor(0, 0, 0); 2412 context.SetSourceColor(0, 0, 0);
2323 cairo_paint(cr); 2413 cairo_paint(cr);
2324 } 2414 }
2325 2415
2326 stack_.DrawControls(context, view.GetZoom()); 2416 if (hasSelection_)
2417 {
2418 stack_.DrawBorder(context, selectedBitmap_, view.GetZoom());
2419 }
2327 2420
2328 return true; 2421 return true;
2329 } 2422 }
2330 2423
2331 public: 2424 public:
2332 BitmapStackWidget(MessageBroker& broker, 2425 BitmapStackWidget(MessageBroker& broker,
2333 BitmapStack& stack, 2426 BitmapStack& stack,
2334 const std::string& name) : 2427 const std::string& name) :
2335 WorldSceneWidget(name), 2428 WorldSceneWidget(name),
2336 IObservable(broker),
2337 IObserver(broker), 2429 IObserver(broker),
2338 stack_(stack), 2430 stack_(stack),
2339 invert_(false), 2431 invert_(false),
2340 interpolation_(ImageInterpolation_Nearest) 2432 interpolation_(ImageInterpolation_Nearest),
2433 hasSelection_(false),
2434 selectedBitmap_(0) // Dummy initialization
2341 { 2435 {
2342 stack.RegisterObserverCallback(new Callable<BitmapStackWidget, BitmapStack::GeometryChangedMessage>(*this, &BitmapStackWidget::OnGeometryChanged)); 2436 stack.RegisterObserverCallback(new Callable<BitmapStackWidget, BitmapStack::GeometryChangedMessage>(*this, &BitmapStackWidget::OnGeometryChanged));
2343 stack.RegisterObserverCallback(new Callable<BitmapStackWidget, BitmapStack::ContentChangedMessage>(*this, &BitmapStackWidget::OnContentChanged)); 2437 stack.RegisterObserverCallback(new Callable<BitmapStackWidget, BitmapStack::ContentChangedMessage>(*this, &BitmapStackWidget::OnContentChanged));
2344 } 2438 }
2345 2439
2346 BitmapStack& GetStack() const 2440 BitmapStack& GetStack() const
2347 { 2441 {
2348 return stack_; 2442 return stack_;
2349 } 2443 }
2350 2444
2445 void Unselect()
2446 {
2447 hasSelection_ = false;
2448 }
2449
2450 void Select(size_t bitmap)
2451 {
2452 hasSelection_ = true;
2453 selectedBitmap_ = bitmap;
2454 }
2455
2456 bool LookupSelectedBitmap(size_t& bitmap)
2457 {
2458 if (hasSelection_)
2459 {
2460 bitmap = selectedBitmap_;
2461 return true;
2462 }
2463 else
2464 {
2465 return false;
2466 }
2467 }
2468
2351 void OnGeometryChanged(const BitmapStack::GeometryChangedMessage& message) 2469 void OnGeometryChanged(const BitmapStack::GeometryChangedMessage& message)
2352 { 2470 {
2353 LOG(INFO) << "Geometry has changed"; 2471 LOG(INFO) << "Geometry has changed";
2354 FitContent(); 2472 FitContent();
2355 } 2473 }
2373 { 2491 {
2374 invert_ = !invert_; 2492 invert_ = !invert_;
2375 NotifyContentChanged(); 2493 NotifyContentChanged();
2376 } 2494 }
2377 2495
2378 bool IsInvert() const 2496 bool IsInverted() const
2379 { 2497 {
2380 return invert_; 2498 return invert_;
2381 } 2499 }
2382 2500
2383 void SetInterpolation(ImageInterpolation interpolation) 2501 void SetInterpolation(ImageInterpolation interpolation)
2411 }; 2529 };
2412 2530
2413 2531
2414 UndoRedoStack undoRedoStack_; 2532 UndoRedoStack undoRedoStack_;
2415 Tool tool_; 2533 Tool tool_;
2416 OrthancApiClient *orthanc_;
2417 2534
2418 2535
2419 static double GetHandleSize() 2536 static double GetHandleSize()
2420 { 2537 {
2421 return 10.0; 2538 return 10.0;
2422 } 2539 }
2423 2540
2424 2541
2425 static BitmapStackWidget& GetWidget(WorldSceneWidget& widget)
2426 {
2427 return dynamic_cast<BitmapStackWidget&>(widget);
2428 }
2429
2430
2431 static BitmapStack& GetStack(WorldSceneWidget& widget)
2432 {
2433 return GetWidget(widget).GetStack();
2434 }
2435
2436
2437 public: 2542 public:
2438 BitmapStackInteractor(MessageBroker& broker) : 2543 BitmapStackInteractor(MessageBroker& broker) :
2439 IObserver(broker), 2544 IObserver(broker),
2440 tool_(Tool_Move), 2545 tool_(Tool_Move)
2441 orthanc_(NULL)
2442 { 2546 {
2443 } 2547 }
2444 2548
2445 virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, 2549 virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& worldWidget,
2446 const ViewportGeometry& view, 2550 const ViewportGeometry& view,
2447 MouseButton button, 2551 MouseButton button,
2448 KeyboardModifiers modifiers, 2552 KeyboardModifiers modifiers,
2449 int viewportX, 2553 int viewportX,
2450 int viewportY, 2554 int viewportY,
2451 double x, 2555 double x,
2452 double y, 2556 double y,
2453 IStatusBar* statusBar) 2557 IStatusBar* statusBar)
2454 { 2558 {
2559 BitmapStackWidget& widget = dynamic_cast<BitmapStackWidget&>(worldWidget);
2560
2455 if (button == MouseButton_Left) 2561 if (button == MouseButton_Left)
2456 { 2562 {
2457 size_t selected; 2563 size_t selected;
2458 2564
2459 if (tool_ == Tool_Windowing) 2565 if (tool_ == Tool_Windowing)
2460 { 2566 {
2461 return new WindowingTracker(undoRedoStack_, GetStack(widget), 2567 return new WindowingTracker(undoRedoStack_, widget.GetStack(),
2462 viewportX, viewportY, 2568 viewportX, viewportY,
2463 WindowingTracker::Action_DecreaseWidth, 2569 WindowingTracker::Action_DecreaseWidth,
2464 WindowingTracker::Action_IncreaseWidth, 2570 WindowingTracker::Action_IncreaseWidth,
2465 WindowingTracker::Action_DecreaseCenter, 2571 WindowingTracker::Action_DecreaseCenter,
2466 WindowingTracker::Action_IncreaseCenter); 2572 WindowingTracker::Action_IncreaseCenter);
2467 } 2573 }
2468 else if (!GetStack(widget).GetSelectedBitmap(selected)) 2574 else if (!widget.LookupSelectedBitmap(selected))
2469 { 2575 {
2576 // No bitmap is currently selected
2470 size_t bitmap; 2577 size_t bitmap;
2471 if (GetStack(widget).LookupBitmap(bitmap, x, y)) 2578 if (widget.GetStack().LookupBitmap(bitmap, x, y))
2472 { 2579 {
2473 LOG(INFO) << "Click on bitmap " << bitmap; 2580 widget.Select(bitmap);
2474 GetStack(widget).Select(bitmap);
2475 } 2581 }
2476 2582
2477 return NULL; 2583 return NULL;
2478 } 2584 }
2479 else if (tool_ == Tool_Crop || 2585 else if (tool_ == Tool_Crop ||
2480 tool_ == Tool_Resize) 2586 tool_ == Tool_Resize)
2481 { 2587 {
2482 BitmapStack::BitmapAccessor accessor(GetStack(widget), selected); 2588 BitmapStack::BitmapAccessor accessor(widget.GetStack(), selected);
2483 BitmapStack::Corner corner; 2589 BitmapStack::Corner corner;
2484 if (accessor.GetBitmap().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize())) 2590 if (accessor.GetBitmap().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize()))
2485 { 2591 {
2486 switch (tool_) 2592 switch (tool_)
2487 { 2593 {
2488 case Tool_Crop: 2594 case Tool_Crop:
2489 return new CropBitmapTracker(undoRedoStack_, GetStack(widget), view, selected, x, y, corner); 2595 return new CropBitmapTracker(undoRedoStack_, widget.GetStack(), view, selected, x, y, corner);
2490 2596
2491 case Tool_Resize: 2597 case Tool_Resize:
2492 return new ResizeBitmapTracker(undoRedoStack_, GetStack(widget), selected, x, y, corner, 2598 return new ResizeBitmapTracker(undoRedoStack_, widget.GetStack(), selected, x, y, corner,
2493 (modifiers & KeyboardModifiers_Shift)); 2599 (modifiers & KeyboardModifiers_Shift));
2494 2600
2495 default: 2601 default:
2496 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); 2602 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
2497 } 2603 }
2498 } 2604 }
2499 else 2605 else
2500 { 2606 {
2501 size_t bitmap; 2607 size_t bitmap;
2502 2608
2503 if (!GetStack(widget).LookupBitmap(bitmap, x, y) || 2609 if (widget.GetStack().LookupBitmap(bitmap, x, y))
2504 bitmap != selected)
2505 { 2610 {
2506 GetStack(widget).Unselect(); 2611 widget.Select(bitmap);
2612 }
2613 else
2614 {
2615 widget.Unselect();
2507 } 2616 }
2508 2617
2509 return NULL; 2618 return NULL;
2510 } 2619 }
2511 } 2620 }
2512 else 2621 else
2513 { 2622 {
2514 size_t bitmap; 2623 size_t bitmap;
2515 2624
2516 if (GetStack(widget).LookupBitmap(bitmap, x, y) && 2625 if (widget.GetStack().LookupBitmap(bitmap, x, y))
2517 bitmap == selected) 2626 {
2518 { 2627 if (bitmap == selected)
2519 switch (tool_)
2520 { 2628 {
2521 case Tool_Move: 2629 switch (tool_)
2522 return new MoveBitmapTracker(undoRedoStack_, GetStack(widget), bitmap, x, y, 2630 {
2523 (modifiers & KeyboardModifiers_Shift)); 2631 case Tool_Move:
2524 2632 return new MoveBitmapTracker(undoRedoStack_, widget.GetStack(), bitmap, x, y,
2525 case Tool_Rotate:
2526 return new RotateBitmapTracker(undoRedoStack_, GetStack(widget), view, bitmap, x, y,
2527 (modifiers & KeyboardModifiers_Shift)); 2633 (modifiers & KeyboardModifiers_Shift));
2634
2635 case Tool_Rotate:
2636 return new RotateBitmapTracker(undoRedoStack_, widget.GetStack(), view, bitmap, x, y,
2637 (modifiers & KeyboardModifiers_Shift));
2528 2638
2529 default: 2639 default:
2530 break; 2640 break;
2641 }
2642
2643 return NULL;
2531 } 2644 }
2532 2645 else
2646 {
2647 widget.Select(bitmap);
2648 return NULL;
2649 }
2650 }
2651 else
2652 {
2653 widget.Unselect();
2533 return NULL; 2654 return NULL;
2534 } 2655 }
2535 else
2536 {
2537 LOG(INFO) << "Click out of any bitmap";
2538 GetStack(widget).Unselect();
2539 return NULL;
2540 }
2541 } 2656 }
2542 } 2657 }
2543 else 2658 else
2544 { 2659 {
2545 return NULL; 2660 return NULL;
2546 } 2661 }
2547 } 2662 }
2548 2663
2549 virtual void MouseOver(CairoContext& context, 2664 virtual void MouseOver(CairoContext& context,
2550 WorldSceneWidget& widget, 2665 WorldSceneWidget& worldWidget,
2551 const ViewportGeometry& view, 2666 const ViewportGeometry& view,
2552 double x, 2667 double x,
2553 double y, 2668 double y,
2554 IStatusBar* statusBar) 2669 IStatusBar* statusBar)
2555 { 2670 {
2671 BitmapStackWidget& widget = dynamic_cast<BitmapStackWidget&>(worldWidget);
2672
2556 #if 0 2673 #if 0
2557 if (statusBar != NULL) 2674 if (statusBar != NULL)
2558 { 2675 {
2559 char buf[64]; 2676 char buf[64];
2560 sprintf(buf, "X = %.02f Y = %.02f (in cm)", x / 10.0, y / 10.0); 2677 sprintf(buf, "X = %.02f Y = %.02f (in cm)", x / 10.0, y / 10.0);
2561 statusBar->SetMessage(buf); 2678 statusBar->SetMessage(buf);
2562 } 2679 }
2563 #endif 2680 #endif
2564 2681
2565 size_t selected; 2682 size_t selected;
2566 if (GetStack(widget).GetSelectedBitmap(selected) && 2683
2684 if (widget.LookupSelectedBitmap(selected) &&
2567 (tool_ == Tool_Crop || 2685 (tool_ == Tool_Crop ||
2568 tool_ == Tool_Resize)) 2686 tool_ == Tool_Resize))
2569 { 2687 {
2570 BitmapStack::BitmapAccessor accessor(GetStack(widget), selected); 2688 BitmapStack::BitmapAccessor accessor(widget.GetStack(), selected);
2571 2689
2572 BitmapStack::Corner corner; 2690 BitmapStack::Corner corner;
2573 if (accessor.GetBitmap().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize())) 2691 if (accessor.GetBitmap().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize()))
2574 { 2692 {
2575 accessor.GetBitmap().GetCorner(x, y, corner); 2693 accessor.GetBitmap().GetCorner(x, y, corner);
2594 KeyboardModifiers modifiers, 2712 KeyboardModifiers modifiers,
2595 IStatusBar* statusBar) 2713 IStatusBar* statusBar)
2596 { 2714 {
2597 } 2715 }
2598 2716
2599 virtual void KeyPressed(WorldSceneWidget& widget, 2717 virtual void KeyPressed(WorldSceneWidget& worldWidget,
2600 KeyboardKeys key, 2718 KeyboardKeys key,
2601 char keyChar, 2719 char keyChar,
2602 KeyboardModifiers modifiers, 2720 KeyboardModifiers modifiers,
2603 IStatusBar* statusBar) 2721 IStatusBar* statusBar)
2604 { 2722 {
2723 BitmapStackWidget& widget = dynamic_cast<BitmapStackWidget&>(worldWidget);
2724
2605 switch (keyChar) 2725 switch (keyChar)
2606 { 2726 {
2607 case 'a': 2727 case 'a':
2608 widget.FitContent(); 2728 widget.FitContent();
2609 break; 2729 break;
2632 tags.SetValue(Orthanc::DICOM_TAG_SERIES_NUMBER, "1", false); 2752 tags.SetValue(Orthanc::DICOM_TAG_SERIES_NUMBER, "1", false);
2633 tags.SetValue(Orthanc::DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.1", false); 2753 tags.SetValue(Orthanc::DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.1", false);
2634 tags.SetValue(Orthanc::DICOM_TAG_STUDY_ID, "STUDY", false); 2754 tags.SetValue(Orthanc::DICOM_TAG_STUDY_ID, "STUDY", false);
2635 tags.SetValue(Orthanc::DICOM_TAG_VIEW_POSITION, "", false); 2755 tags.SetValue(Orthanc::DICOM_TAG_VIEW_POSITION, "", false);
2636 2756
2637 Export(GetWidget(widget), 0.1, 0.1, tags); 2757 widget.GetStack().Export(tags, 0.1, 0.1, widget.IsInverted(), widget.GetInterpolation());
2638 break; 2758 break;
2639 } 2759 }
2640 2760
2641 case 'i': 2761 case 'i':
2642 GetWidget(widget).SwitchInvert(); 2762 widget.SwitchInvert();
2643 break; 2763 break;
2644 2764
2645 case 'm': 2765 case 'm':
2646 tool_ = Tool_Move; 2766 tool_ = Tool_Move;
2647 break; 2767 break;
2648 2768
2649 case 'n': 2769 case 'n':
2650 { 2770 {
2651 switch (GetWidget(widget).GetInterpolation()) 2771 switch (widget.GetInterpolation())
2652 { 2772 {
2653 case ImageInterpolation_Nearest: 2773 case ImageInterpolation_Nearest:
2654 LOG(INFO) << "Switching to bilinear interpolation"; 2774 LOG(INFO) << "Switching to bilinear interpolation";
2655 GetWidget(widget).SetInterpolation(ImageInterpolation_Bilinear); 2775 widget.SetInterpolation(ImageInterpolation_Bilinear);
2656 break; 2776 break;
2657 2777
2658 case ImageInterpolation_Bilinear: 2778 case ImageInterpolation_Bilinear:
2659 LOG(INFO) << "Switching to nearest neighbor interpolation"; 2779 LOG(INFO) << "Switching to nearest neighbor interpolation";
2660 GetWidget(widget).SetInterpolation(ImageInterpolation_Nearest); 2780 widget.SetInterpolation(ImageInterpolation_Nearest);
2661 break; 2781 break;
2662 2782
2663 default: 2783 default:
2664 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); 2784 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
2665 } 2785 }
2696 break; 2816 break;
2697 2817
2698 default: 2818 default:
2699 break; 2819 break;
2700 } 2820 }
2701 }
2702
2703
2704 void SetOrthanc(OrthancApiClient& orthanc)
2705 {
2706 orthanc_ = &orthanc;
2707 }
2708
2709
2710 void Export(const BitmapStackWidget& widget,
2711 double pixelSpacingX,
2712 double pixelSpacingY,
2713 const Orthanc::DicomMap& dicom)
2714 {
2715 if (pixelSpacingX <= 0 ||
2716 pixelSpacingY <= 0)
2717 {
2718 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
2719 }
2720
2721 if (orthanc_ == NULL)
2722 {
2723 return;
2724 }
2725
2726 LOG(WARNING) << "Exporting DICOM";
2727
2728 Extent2D extent = widget.GetStack().GetSceneExtent();
2729
2730 int w = std::ceil(extent.GetWidth() / pixelSpacingX);
2731 int h = std::ceil(extent.GetHeight() / pixelSpacingY);
2732
2733 if (w < 0 || h < 0)
2734 {
2735 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
2736 }
2737
2738 Orthanc::Image layers(Orthanc::PixelFormat_Float32,
2739 static_cast<unsigned int>(w),
2740 static_cast<unsigned int>(h), false);
2741
2742 Matrix view = LinearAlgebra::Product(
2743 CreateScalingMatrix(1.0 / pixelSpacingX, 1.0 / pixelSpacingY),
2744 CreateOffsetMatrix(-extent.GetX1(), -extent.GetY1()));
2745
2746 widget.GetStack().Render(layers, view, widget.GetInterpolation());
2747
2748 Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16,
2749 layers.GetWidth(), layers.GetHeight(), false);
2750 Orthanc::ImageProcessing::Convert(rendered, layers);
2751
2752 std::string base64;
2753
2754 {
2755 std::string content;
2756
2757 #if EXPORT_USING_PAM == 1
2758 {
2759 Orthanc::PamWriter writer;
2760 writer.WriteToMemory(content, rendered);
2761 }
2762 #else
2763 {
2764 Orthanc::PngWriter writer;
2765 writer.WriteToMemory(content, rendered);
2766 }
2767 #endif
2768
2769 Orthanc::Toolbox::EncodeBase64(base64, content);
2770 }
2771
2772 std::set<Orthanc::DicomTag> tags;
2773 dicom.GetTags(tags);
2774
2775 Json::Value json = Json::objectValue;
2776 json["Tags"] = Json::objectValue;
2777
2778 for (std::set<Orthanc::DicomTag>::const_iterator
2779 tag = tags.begin(); tag != tags.end(); ++tag)
2780 {
2781 const Orthanc::DicomValue& value = dicom.GetValue(*tag);
2782 if (!value.IsNull() &&
2783 !value.IsBinary())
2784 {
2785 json["Tags"][tag->Format()] = value.GetContent();
2786 }
2787 }
2788
2789 json["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] =
2790 (widget.IsInvert() ? "MONOCHROME1" : "MONOCHROME2");
2791
2792
2793 // WARNING: The order of PixelSpacing is Y/X
2794 char buf[32];
2795 sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX);
2796
2797 json["Tags"][Orthanc::DICOM_TAG_PIXEL_SPACING.Format()] = buf;
2798
2799 float center, width;
2800 if (widget.GetStack().GetWindowing(center, width))
2801 {
2802 json["Tags"][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()] =
2803 boost::lexical_cast<std::string>(boost::math::iround(center));
2804
2805 json["Tags"][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()] =
2806 boost::lexical_cast<std::string>(boost::math::iround(width));
2807 }
2808
2809 #if EXPORT_USING_PAM == 1
2810 json["Content"] = "data:" + std::string(Orthanc::MIME_PAM) + ";base64," + base64;
2811 #else
2812 json["Content"] = "data:" + std::string(Orthanc::MIME_PNG) + ";base64," + base64;
2813 #endif
2814
2815 orthanc_->PostJsonAsyncExpectJson(
2816 "/tools/create-dicom", json,
2817 new Callable<BitmapStackInteractor, OrthancApiClient::JsonResponseReadyMessage>
2818 (*this, &BitmapStackInteractor::OnDicomExported),
2819 NULL, NULL);
2820 }
2821
2822
2823 void OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message)
2824 {
2825 LOG(WARNING) << "DICOM export was successful:"
2826 << message.GetJson().toStyledString();
2827 } 2821 }
2828 }; 2822 };
2829 2823
2830 2824
2831 2825
2843 public: 2837 public:
2844 SingleFrameEditorApplication(MessageBroker& broker) : 2838 SingleFrameEditorApplication(MessageBroker& broker) :
2845 IObserver(broker), 2839 IObserver(broker),
2846 interactor_(broker) 2840 interactor_(broker)
2847 { 2841 {
2842 }
2843
2844 virtual ~SingleFrameEditorApplication()
2845 {
2846 LOG(WARNING) << "Destroying the application";
2848 } 2847 }
2849 2848
2850 virtual void DeclareStartupOptions(boost::program_options::options_description& options) 2849 virtual void DeclareStartupOptions(boost::program_options::options_description& options)
2851 { 2850 {
2852 boost::program_options::options_description generic("Sample options"); 2851 boost::program_options::options_description generic("Sample options");
2890 2889
2891 std::string instance = parameters["instance"].as<std::string>(); 2890 std::string instance = parameters["instance"].as<std::string>();
2892 int frame = parameters["frame"].as<unsigned int>(); 2891 int frame = parameters["frame"].as<unsigned int>();
2893 2892
2894 orthancApiClient_.reset(new OrthancApiClient(IObserver::broker_, context_->GetWebService())); 2893 orthancApiClient_.reset(new OrthancApiClient(IObserver::broker_, context_->GetWebService()));
2895 interactor_.SetOrthanc(*orthancApiClient_);
2896 2894
2897 Orthanc::FontRegistry fonts; 2895 Orthanc::FontRegistry fonts;
2898 fonts.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); 2896 fonts.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16);
2899 2897
2900 stack_.reset(new BitmapStack(IObserver::broker_, *orthancApiClient_)); 2898 stack_.reset(new BitmapStack(IObserver::broker_, *orthancApiClient_));