Mercurial > hg > orthanc-wsi
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 { |