Mercurial > hg > orthanc-stone
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_)); |