comparison ViewerPlugin/Plugin.cpp @ 261:c72fbdecdc38 iiif

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 10 Jul 2023 08:59:40 +0200
parents 35c241231af2
children b9eab260a372
comparison
equal deleted inserted replaced
260:35c241231af2 261:c72fbdecdc38
21 21
22 22
23 #include "../Framework/PrecompiledHeadersWSI.h" 23 #include "../Framework/PrecompiledHeadersWSI.h"
24 24
25 #include "DicomPyramidCache.h" 25 #include "DicomPyramidCache.h"
26 #include "OrthancPluginConnection.h" 26 #include "IIIF.h"
27 #include "RawTile.h" 27 #include "RawTile.h"
28 28
29 #include <Compatibility.h> // For std::unique_ptr 29 #include <Compatibility.h> // For std::unique_ptr
30 #include <Logging.h> 30 #include <Logging.h>
31 #include <Images/Image.h> 31 #include <Images/Image.h>
36 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" 36 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
37 37
38 #include <EmbeddedResources.h> 38 #include <EmbeddedResources.h>
39 39
40 #include <cassert> 40 #include <cassert>
41 #include <boost/regex.hpp>
42 #include <boost/math/special_functions/round.hpp>
43
44 static std::unique_ptr<OrthancWSI::OrthancPluginConnection> orthanc_;
45 static std::unique_ptr<OrthancWSI::DicomPyramidCache> cache_;
46 static std::string publicIIIFUrl_;
47 41
48 static void AnswerSparseTile(OrthancPluginRestOutput* output, 42 static void AnswerSparseTile(OrthancPluginRestOutput* output,
49 unsigned int tileWidth, 43 unsigned int tileWidth,
50 unsigned int tileHeight) 44 unsigned int tileHeight)
51 { 45 {
83 char tmp[1024]; 77 char tmp[1024];
84 sprintf(tmp, "Accessing whole-slide pyramid of series %s", seriesId.c_str()); 78 sprintf(tmp, "Accessing whole-slide pyramid of series %s", seriesId.c_str());
85 OrthancPluginLogInfo(OrthancPlugins::GetGlobalContext(), tmp); 79 OrthancPluginLogInfo(OrthancPlugins::GetGlobalContext(), tmp);
86 80
87 81
88 OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId); 82 OrthancWSI::DicomPyramidCache::Locker locker(seriesId);
89 83
90 unsigned int totalWidth = locker.GetPyramid().GetLevelWidth(0); 84 unsigned int totalWidth = locker.GetPyramid().GetLevelWidth(0);
91 unsigned int totalHeight = locker.GetPyramid().GetLevelHeight(0); 85 unsigned int totalHeight = locker.GetPyramid().GetLevelHeight(0);
92 86
93 Json::Value sizes = Json::arrayValue; 87 Json::Value sizes = Json::arrayValue;
152 { 146 {
153 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); 147 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
154 } 148 }
155 149
156 // Retrieve the raw tile from the WSI pyramid 150 // Retrieve the raw tile from the WSI pyramid
157 std::unique_ptr<RawTile> rawTile; 151 std::unique_ptr<OrthancWSI::RawTile> rawTile;
158 152
159 { 153 {
160 OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId); 154 OrthancWSI::DicomPyramidCache::Locker locker(seriesId);
161 rawTile.reset(new RawTile(locker.GetPyramid(), 155 rawTile.reset(new OrthancWSI::RawTile(locker.GetPyramid(),
162 static_cast<unsigned int>(level), 156 static_cast<unsigned int>(level),
163 static_cast<unsigned int>(tileX), 157 static_cast<unsigned int>(tileX),
164 static_cast<unsigned int>(tileY))); 158 static_cast<unsigned int>(tileY)));
165 } 159 }
166 160
167 /** 161 /**
168 * In the case the DICOM file doesn't use the JPEG transfer syntax, 162 * In the case the DICOM file doesn't use the JPEG transfer syntax,
169 * transfer the tile (which is presumably lossless) as a PNG image 163 * transfer the tile (which is presumably lossless) as a PNG image
183 { 177 {
184 char tmp[1024]; 178 char tmp[1024];
185 sprintf(tmp, "New instance has been added to series %s, invalidating it", resourceId); 179 sprintf(tmp, "New instance has been added to series %s, invalidating it", resourceId);
186 OrthancPluginLogInfo(OrthancPlugins::GetGlobalContext(), tmp); 180 OrthancPluginLogInfo(OrthancPlugins::GetGlobalContext(), tmp);
187 181
188 cache_->Invalidate(resourceId); 182 OrthancWSI::DicomPyramidCache::GetInstance().Invalidate(resourceId);
189 } 183 }
190 184
191 return OrthancPluginErrorCode_Success; 185 return OrthancPluginErrorCode_Success;
192 } 186 }
193 187
229 223
230 std::string content; 224 std::string content;
231 Orthanc::EmbeddedResources::GetFileResource(content, resource); 225 Orthanc::EmbeddedResources::GetFileResource(content, resource);
232 226
233 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, content.c_str(), content.size(), mime.c_str()); 227 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, content.c_str(), content.size(), mime.c_str());
234 }
235
236
237
238 void ServeIIIFImageInfo(OrthancPluginRestOutput* output,
239 const char* url,
240 const OrthancPluginHttpRequest* request)
241 {
242 std::string seriesId(request->groups[0]);
243
244 LOG(INFO) << "IIIF: Image API call to whole-slide pyramid of series " << seriesId;
245
246 OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId);
247
248 if (locker.GetPyramid().GetLevelCount() == 0)
249 {
250 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
251 }
252
253 if (locker.GetPyramid().GetTileWidth(0) != locker.GetPyramid().GetTileHeight(0))
254 {
255 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat,
256 "IIIF doesn't support non-isotropic tile sizes");
257 }
258
259 for (unsigned int i = 1; i < locker.GetPyramid().GetLevelCount(); i++)
260 {
261 if (locker.GetPyramid().GetTileWidth(i) != locker.GetPyramid().GetTileWidth(0) ||
262 locker.GetPyramid().GetTileHeight(i) != locker.GetPyramid().GetTileHeight(0))
263 {
264 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat,
265 "IIIF doesn't support levels with varying tile sizes");
266 }
267 }
268
269 Json::Value sizes = Json::arrayValue;
270 Json::Value scaleFactors = Json::arrayValue;
271
272 for (unsigned int i = locker.GetPyramid().GetLevelCount(); i > 0; i--)
273 {
274 /**
275 * Openseadragon seems to have difficulties in rendering
276 * non-integer scale factors. Consequently, we only keep the
277 * levels with an integer scale factor.
278 **/
279 if (locker.GetPyramid().GetLevelWidth(0) % locker.GetPyramid().GetLevelWidth(i - 1) == 0 &&
280 locker.GetPyramid().GetLevelHeight(0) % locker.GetPyramid().GetLevelHeight(i - 1) == 0)
281 {
282 Json::Value level;
283 level["width"] = locker.GetPyramid().GetLevelWidth(i - 1);
284 level["height"] = locker.GetPyramid().GetLevelHeight(i - 1);
285 sizes.append(level);
286
287 scaleFactors.append(static_cast<float>(locker.GetPyramid().GetLevelWidth(0)) /
288 static_cast<float>(locker.GetPyramid().GetLevelWidth(i - 1)));
289 }
290 }
291
292 Json::Value tiles;
293 tiles["width"] = locker.GetPyramid().GetTileWidth(0);
294 tiles["height"] = locker.GetPyramid().GetTileHeight(0);
295 tiles["scaleFactors"] = scaleFactors;
296
297 Json::Value result;
298 result["@context"] = "http://iiif.io/api/image/2/context.json";
299 result["@id"] = publicIIIFUrl_ + seriesId;
300 result["profile"] = "http://iiif.io/api/image/2/level0.json";
301 result["protocol"] = "http://iiif.io/api/image";
302 result["width"] = locker.GetPyramid().GetLevelWidth(0);
303 result["height"] = locker.GetPyramid().GetLevelHeight(0);
304 result["sizes"] = sizes;
305
306 result["tiles"] = Json::arrayValue;
307 result["tiles"].append(tiles);
308
309 std::string s = result.toStyledString();
310 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), "application/json");
311 }
312
313
314 static unsigned int GetPhysicalTileWidth(const OrthancWSI::ITiledPyramid& pyramid,
315 unsigned int level)
316 {
317 return static_cast<unsigned int>(boost::math::iround(
318 static_cast<float>(pyramid.GetTileWidth(level)) *
319 static_cast<float>(pyramid.GetLevelWidth(0)) /
320 static_cast<float>(pyramid.GetLevelWidth(level))));
321 }
322
323
324 static unsigned int GetPhysicalTileHeight(const OrthancWSI::ITiledPyramid& pyramid,
325 unsigned int level)
326 {
327 return static_cast<unsigned int>(boost::math::iround(
328 static_cast<float>(pyramid.GetTileHeight(level)) *
329 static_cast<float>(pyramid.GetLevelHeight(0)) /
330 static_cast<float>(pyramid.GetLevelHeight(level))));
331 }
332
333
334 void ServeIIIFImageTile(OrthancPluginRestOutput* output,
335 const char* url,
336 const OrthancPluginHttpRequest* request)
337 {
338 std::string seriesId(request->groups[0]);
339 std::string region(request->groups[1]);
340 std::string size(request->groups[2]);
341 std::string rotation(request->groups[3]);
342 std::string quality(request->groups[4]);
343 std::string format(request->groups[5]);
344
345 LOG(INFO) << "IIIF: Image API call to tile of series " << seriesId << ": "
346 << "region=" << region << "; size=" << size << "; rotation="
347 << rotation << "; quality=" << quality << "; format=" << format;
348
349 if (rotation != "0")
350 {
351 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Unsupported rotation: " + rotation);
352 }
353
354 if (quality != "default")
355 {
356 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Unsupported quality: " + quality);
357 }
358
359 if (format != "jpg")
360 {
361 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Unsupported format: " + format);
362 }
363
364 if (region == "full")
365 {
366 OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId);
367
368 OrthancWSI::ITiledPyramid& pyramid = locker.GetPyramid();
369 const unsigned int level = pyramid.GetLevelCount() - 1;
370
371 Orthanc::Image full(Orthanc::PixelFormat_RGB24, pyramid.GetLevelWidth(level), pyramid.GetLevelHeight(level), false);
372 Orthanc::ImageProcessing::Set(full, 255, 255, 255, 0);
373
374 const unsigned int nx = OrthancWSI::CeilingDivision(pyramid.GetLevelWidth(level), pyramid.GetTileWidth(level));
375 const unsigned int ny = OrthancWSI::CeilingDivision(pyramid.GetLevelHeight(level), pyramid.GetTileHeight(level));
376 for (unsigned int ty = 0; ty < ny; ty++)
377 {
378 const unsigned int y = ty * pyramid.GetTileHeight(level);
379 const unsigned int height = std::min(pyramid.GetTileHeight(level), full.GetHeight() - y);
380
381 for (unsigned int tx = 0; tx < nx; tx++)
382 {
383 const unsigned int x = tx * pyramid.GetTileWidth(level);
384 std::unique_ptr<Orthanc::ImageAccessor> tile(pyramid.DecodeTile(level, tx, ty));
385
386 const unsigned int width = std::min(pyramid.GetTileWidth(level), full.GetWidth() - x);
387
388 Orthanc::ImageAccessor source, target;
389 tile->GetRegion(source, 0, 0, width, height);
390 full.GetRegion(target, x, y, width, height);
391
392 Orthanc::ImageProcessing::Copy(target, source);
393 }
394 }
395
396 std::string encoded;
397 RawTile::Encode(encoded, full, Orthanc::MimeType_Jpeg);
398
399 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, encoded.c_str(),
400 encoded.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg));
401 }
402 else
403 {
404 int regionX, regionY, regionWidth, regionHeight;
405
406 bool ok = false;
407 boost::regex regionPattern("([0-9]+),([0-9]+),([0-9]+),([0-9]+)");
408 boost::cmatch regionWhat;
409 if (regex_match(region.c_str(), regionWhat, regionPattern))
410 {
411 try
412 {
413 regionX = boost::lexical_cast<int>(regionWhat[1]);
414 regionY = boost::lexical_cast<int>(regionWhat[2]);
415 regionWidth = boost::lexical_cast<int>(regionWhat[3]);
416 regionHeight = boost::lexical_cast<int>(regionWhat[4]);
417 ok = (regionX >= 0 &&
418 regionY >= 0 &&
419 regionWidth > 0 &&
420 regionHeight > 0);
421 }
422 catch (boost::bad_lexical_cast&)
423 {
424 }
425 }
426
427 if (!ok)
428 {
429 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Not a (x,y,width,height) region: " + region);
430 }
431
432 int cropWidth;
433 boost::regex sizePattern("([0-9]+),");
434 boost::cmatch sizeWhat;
435 if (regex_match(size.c_str(), sizeWhat, sizePattern))
436 {
437 try
438 {
439 cropWidth = boost::lexical_cast<int>(sizeWhat[1]);
440 ok = (cropWidth > 0);
441 }
442 catch (boost::bad_lexical_cast&)
443 {
444 }
445 }
446
447 if (!ok)
448 {
449 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Not a (width,) size: " + size);
450 }
451
452 std::unique_ptr<RawTile> rawTile;
453 std::unique_ptr<Orthanc::ImageAccessor> toCrop;
454
455 {
456 OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId);
457
458 OrthancWSI::ITiledPyramid& pyramid = locker.GetPyramid();
459
460 unsigned int level;
461 for (level = 0; level < pyramid.GetLevelCount(); level++)
462 {
463 const unsigned int physicalTileWidth = GetPhysicalTileWidth(pyramid, level);
464 const unsigned int physicalTileHeight = GetPhysicalTileHeight(pyramid, level);
465
466 if (regionX % physicalTileWidth == 0 &&
467 regionY % physicalTileHeight == 0 &&
468 regionWidth <= physicalTileWidth &&
469 regionHeight <= physicalTileHeight &&
470 regionX + regionWidth <= pyramid.GetLevelWidth(0) &&
471 regionY + regionHeight <= pyramid.GetLevelHeight(0))
472 {
473 break;
474 }
475 }
476
477 if (level == pyramid.GetLevelCount())
478 {
479 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "IIIF - Cannot locate the level of interest");
480 }
481 else if (cropWidth > pyramid.GetTileWidth(level))
482 {
483 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "IIIF - Request for a cropping that is too large for the tile size");
484 }
485 else
486 {
487 rawTile.reset(new RawTile(locker.GetPyramid(), level,
488 regionX / GetPhysicalTileWidth(pyramid, level),
489 regionY / GetPhysicalTileHeight(pyramid, level)));
490
491 if (cropWidth < pyramid.GetTileWidth(level))
492 {
493 toCrop.reset(rawTile->Decode());
494 rawTile.reset(NULL);
495 }
496 }
497 }
498
499 if (rawTile.get() != NULL)
500 {
501 assert(toCrop.get() == NULL);
502
503 // Level 0 Compliance of IIIF expects JPEG files
504 rawTile->Answer(output, Orthanc::MimeType_Jpeg);
505 }
506 else if (toCrop.get() != NULL)
507 {
508 assert(rawTile.get() == NULL);
509 assert(cropWidth < toCrop->GetWidth());
510
511 Orthanc::ImageAccessor cropped;
512 toCrop->GetRegion(cropped, 0, 0, cropWidth, toCrop->GetHeight());
513
514 std::string encoded;
515 RawTile::Encode(encoded, cropped, Orthanc::MimeType_Jpeg);
516
517 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, encoded.c_str(),
518 encoded.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg));
519 }
520 else
521 {
522 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
523 }
524 }
525 }
526
527
528 void ServeIIIFManifest(OrthancPluginRestOutput* output,
529 const char* url,
530 const OrthancPluginHttpRequest* request)
531 {
532 /**
533 * This is based on IIIF cookbook: "Support Deep Viewing with Basic
534 * Use of a IIIF Image Service."
535 * https://iiif.io/api/cookbook/recipe/0005-image-service/
536 **/
537
538 std::string seriesId(request->groups[0]);
539
540 LOG(INFO) << "IIIF: Presentation API call to whole-slide pyramid of series " << seriesId;
541
542 Json::Value study, series;
543 if (!OrthancPlugins::RestApiGet(series, "/series/" + seriesId, false) ||
544 !OrthancPlugins::RestApiGet(study, "/series/" + seriesId + "/study", false))
545 {
546 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
547 }
548
549 unsigned int width, height;
550
551 {
552 OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId);
553 width = locker.GetPyramid().GetLevelWidth(0);
554 height = locker.GetPyramid().GetLevelHeight(0);
555 }
556
557 const std::string base = publicIIIFUrl_ + seriesId;
558
559 Json::Value service;
560 service["id"] = base;
561 service["profile"] = "level0";
562 service["type"] = "ImageService3";
563
564 Json::Value body;
565 body["id"] = base + "/full/max/0/default.jpg";
566 body["type"] = "Image";
567 body["format"] = Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg);
568 body["height"] = height;
569 body["width"] = width;
570 body["service"].append(service);
571
572 Json::Value annotation;
573 annotation["id"] = base + "/annotation/p0001-image";
574 annotation["type"] = "Annotation";
575 annotation["motivation"] = "painting";
576 annotation["body"] = body;
577 annotation["target"] = base + "/canvas/p1";
578
579 Json::Value annotationPage;
580 annotationPage["id"] = base + "/page/p1/1";
581 annotationPage["type"] = "AnnotationPage";
582 annotationPage["items"].append(annotation);
583
584 Json::Value canvas;
585 canvas["id"] = annotation["target"];
586 canvas["type"] = "Canvas";
587 canvas["width"] = width;
588 canvas["height"] = height;
589
590 Json::Value labels = Json::arrayValue;
591 labels.append(series["MainDicomTags"]["SeriesDate"].asString() + " - " +
592 series["MainDicomTags"]["SeriesDescription"].asString());
593 canvas["label"]["en"] = labels;
594
595 canvas["items"].append(annotationPage);
596
597 Json::Value manifest;
598 manifest["@context"] = "http://iiif.io/api/presentation/3/context.json";
599 manifest["id"] = base + "/manifest.json";
600 manifest["type"] = "Manifest";
601
602 labels = Json::arrayValue;
603 labels.append(study["MainDicomTags"]["StudyDate"].asString() + " - " +
604 study["MainDicomTags"]["StudyDescription"].asString());
605 manifest["label"]["en"] = labels;
606
607 manifest["items"].append(canvas);
608
609 std::string s = manifest.toStyledString();
610 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), "application/json");
611 } 228 }
612 229
613 230
614 extern "C" 231 extern "C"
615 { 232 {
645 262
646 // Limit the number of PNG transcoders to the number of available 263 // Limit the number of PNG transcoders to the number of available
647 // hardware threads (e.g. number of CPUs or cores or 264 // hardware threads (e.g. number of CPUs or cores or
648 // hyperthreading units) 265 // hyperthreading units)
649 unsigned int threads = Orthanc::SystemToolbox::GetHardwareConcurrency(); 266 unsigned int threads = Orthanc::SystemToolbox::GetHardwareConcurrency();
650 RawTile::InitializeTranscoderSemaphore(threads); 267 OrthancWSI::RawTile::InitializeTranscoderSemaphore(threads);
651 268
652 char info[1024]; 269 char info[1024];
653 sprintf(info, "The whole-slide imaging plugin will use at most %u threads to transcode the tiles", threads); 270 sprintf(info, "The whole-slide imaging plugin will use at most %u threads to transcode the tiles", threads);
654 OrthancPluginLogWarning(OrthancPlugins::GetGlobalContext(), info); 271 OrthancPluginLogWarning(OrthancPlugins::GetGlobalContext(), info);
655 272
656 OrthancPluginSetDescription(context, "Provides a Web viewer of whole-slide microscopic images within Orthanc."); 273 OrthancPluginSetDescription(context, "Provides a Web viewer of whole-slide microscopic images within Orthanc.");
657 274
658 orthanc_.reset(new OrthancWSI::OrthancPluginConnection); 275 OrthancWSI::DicomPyramidCache::InitializeInstance(10 /* Number of pyramids to be cached - TODO parameter */);
659 cache_.reset(new OrthancWSI::DicomPyramidCache(*orthanc_, 10 /* Number of pyramids to be cached - TODO parameter */));
660
661 {
662 // TODO => CONFIG
663 publicIIIFUrl_ = "http://localhost:8042/wsi/iiif";
664
665 if (publicIIIFUrl_.empty() ||
666 publicIIIFUrl_[publicIIIFUrl_.size() - 1] != '/')
667 {
668 publicIIIFUrl_ += "/";
669 }
670 }
671 276
672 OrthancPluginRegisterOnChangeCallback(OrthancPlugins::GetGlobalContext(), OnChangeCallback); 277 OrthancPluginRegisterOnChangeCallback(OrthancPlugins::GetGlobalContext(), OnChangeCallback);
673 278
674 OrthancPlugins::RegisterRestCallback<ServeFile>("/wsi/app/(ol.css)", true); 279 OrthancPlugins::RegisterRestCallback<ServeFile>("/wsi/app/(ol.css)", true);
675 OrthancPlugins::RegisterRestCallback<ServeFile>("/wsi/app/(ol.js)", true); 280 OrthancPlugins::RegisterRestCallback<ServeFile>("/wsi/app/(ol.js)", true);
676 OrthancPlugins::RegisterRestCallback<ServeFile>("/wsi/app/(viewer.html)", true); 281 OrthancPlugins::RegisterRestCallback<ServeFile>("/wsi/app/(viewer.html)", true);
677 OrthancPlugins::RegisterRestCallback<ServeFile>("/wsi/app/(viewer.js)", true); 282 OrthancPlugins::RegisterRestCallback<ServeFile>("/wsi/app/(viewer.js)", true);
678 OrthancPlugins::RegisterRestCallback<ServePyramid>("/wsi/pyramids/([0-9a-f-]+)", true); 283 OrthancPlugins::RegisterRestCallback<ServePyramid>("/wsi/pyramids/([0-9a-f-]+)", true);
679 OrthancPlugins::RegisterRestCallback<ServeTile>("/wsi/tiles/([0-9a-f-]+)/([0-9-]+)/([0-9-]+)/([0-9-]+)", true); 284 OrthancPlugins::RegisterRestCallback<ServeTile>("/wsi/tiles/([0-9a-f-]+)/([0-9-]+)/([0-9-]+)/([0-9-]+)", true);
680 285
681 OrthancPlugins::RegisterRestCallback<ServeIIIFImageInfo>("/wsi/iiif/([0-9a-f-]+)/info.json", true); 286 {
682 OrthancPlugins::RegisterRestCallback<ServeIIIFImageTile>("/wsi/iiif/([0-9a-f-]+)/([0-9a-z,:]+)/([0-9a-z,!:]+)/([0-9,!]+)/([a-z]+)\\.([a-z]+)", true); 287 // TODO => CONFIG
683 OrthancPlugins::RegisterRestCallback<ServeIIIFManifest>("/wsi/iiif/([0-9a-f-]+)/manifest.json", true); 288 std::string url = "http://localhost:8042/wsi/iiif";
289
290 if (url.empty() ||
291 url[url.size() - 1] != '/')
292 {
293 url += "/";
294 }
295
296 InitializeIIIF(url);
297 }
684 298
685 // Extend the default Orthanc Explorer with custom JavaScript for WSI 299 // Extend the default Orthanc Explorer with custom JavaScript for WSI
686 std::string explorer; 300 std::string explorer;
687 Orthanc::EmbeddedResources::GetFileResource(explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER); 301 Orthanc::EmbeddedResources::GetFileResource(explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER);
688 OrthancPluginExtendOrthancExplorer(OrthancPlugins::GetGlobalContext(), explorer.c_str()); 302 OrthancPluginExtendOrthancExplorer(OrthancPlugins::GetGlobalContext(), explorer.c_str());
691 } 305 }
692 306
693 307
694 ORTHANC_PLUGINS_API void OrthancPluginFinalize() 308 ORTHANC_PLUGINS_API void OrthancPluginFinalize()
695 { 309 {
696 cache_.reset(NULL); 310 OrthancWSI::DicomPyramidCache::FinalizeInstance();
697 orthanc_.reset(NULL); 311 OrthancWSI::RawTile::FinalizeTranscoderSemaphore();
698 RawTile::FinalizeTranscoderSemaphore();
699 } 312 }
700 313
701 314
702 ORTHANC_PLUGINS_API const char* OrthancPluginGetName() 315 ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
703 { 316 {