comparison ViewerPlugin/IIIF.cpp @ 266:ca605878dc73 iiif

serving non-tiled images using IIIF
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 10 Jul 2023 15:46:19 +0200
parents dab414e5b520
children 45e3b5adf4ae
comparison
equal deleted inserted replaced
265:dab414e5b520 266:ca605878dc73
34 34
35 #include <boost/regex.hpp> 35 #include <boost/regex.hpp>
36 #include <boost/math/special_functions/round.hpp> 36 #include <boost/math/special_functions/round.hpp>
37 37
38 38
39 static const char* const ROWS = "0028,0010";
40 static const char* const COLUMNS = "0028,0011";
41
42
39 static std::string iiifPublicUrl_; 43 static std::string iiifPublicUrl_;
40 44
41 45
42 static void ServeIIIFTiledImageInfo(OrthancPluginRestOutput* output, 46 static void ServeIIIFTiledImageInfo(OrthancPluginRestOutput* output,
43 const char* url, 47 const char* url,
44 const OrthancPluginHttpRequest* request) 48 const OrthancPluginHttpRequest* request)
45 { 49 {
46 std::string seriesId(request->groups[0]); 50 const std::string seriesId(request->groups[0]);
47 51
48 LOG(INFO) << "IIIF: Image API call to whole-slide pyramid of series " << seriesId; 52 LOG(INFO) << "IIIF: Image API call to whole-slide pyramid of series " << seriesId;
49 53
50 OrthancWSI::DicomPyramidCache::Locker locker(seriesId); 54 OrthancWSI::DicomPyramidCache::Locker locker(seriesId);
51 55
109 113
110 result["tiles"] = Json::arrayValue; 114 result["tiles"] = Json::arrayValue;
111 result["tiles"].append(tiles); 115 result["tiles"].append(tiles);
112 116
113 std::string s = result.toStyledString(); 117 std::string s = result.toStyledString();
114 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), "application/json"); 118 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Json));
115 } 119 }
116 120
117 121
118 static unsigned int GetPhysicalTileWidth(const OrthancWSI::ITiledPyramid& pyramid, 122 static unsigned int GetPhysicalTileWidth(const OrthancWSI::ITiledPyramid& pyramid,
119 unsigned int level) 123 unsigned int level)
137 141
138 static void ServeIIIFTiledImageTile(OrthancPluginRestOutput* output, 142 static void ServeIIIFTiledImageTile(OrthancPluginRestOutput* output,
139 const char* url, 143 const char* url,
140 const OrthancPluginHttpRequest* request) 144 const OrthancPluginHttpRequest* request)
141 { 145 {
142 std::string seriesId(request->groups[0]); 146 const std::string seriesId(request->groups[0]);
143 std::string region(request->groups[1]); 147 const std::string region(request->groups[1]);
144 std::string size(request->groups[2]); 148 const std::string size(request->groups[2]);
145 std::string rotation(request->groups[3]); 149 const std::string rotation(request->groups[3]);
146 std::string quality(request->groups[4]); 150 const std::string quality(request->groups[4]);
147 std::string format(request->groups[5]); 151 const std::string format(request->groups[5]);
148 152
149 LOG(INFO) << "IIIF: Image API call to tile of series " << seriesId << ": " 153 LOG(INFO) << "IIIF: Image API call to tile of series " << seriesId << ": "
150 << "region=" << region << "; size=" << size << "; rotation=" 154 << "region=" << region << "; size=" << size << "; rotation="
151 << rotation << "; quality=" << quality << "; format=" << format; 155 << rotation << "; quality=" << quality << "; format=" << format;
152 156
381 const OrthancPluginHttpRequest* request) 385 const OrthancPluginHttpRequest* request)
382 { 386 {
383 static const char* const KEY_INSTANCES = "Instances"; 387 static const char* const KEY_INSTANCES = "Instances";
384 static const char* const SOP_CLASS_UID = "0008,0016"; 388 static const char* const SOP_CLASS_UID = "0008,0016";
385 static const char* const SLICES_SHORT = "SlicesShort"; 389 static const char* const SLICES_SHORT = "SlicesShort";
386 static const char* const ROWS = "0028,0010"; 390
387 static const char* const COLUMNS = "0028,0011"; 391 const std::string seriesId(request->groups[0]);
388 392
389 std::string seriesId(request->groups[0]); 393 LOG(INFO) << "IIIF: Presentation API call to series " << seriesId;
390
391 LOG(INFO) << "IIIF: Presentation API call to whole-slide pyramid of series " << seriesId;
392 394
393 Json::Value study, series; 395 Json::Value study, series;
394 if (!OrthancPlugins::RestApiGet(series, "/series/" + seriesId, false) || 396 if (!OrthancPlugins::RestApiGet(series, "/series/" + seriesId, false) ||
395 !OrthancPlugins::RestApiGet(study, "/series/" + seriesId + "/study", false)) 397 !OrthancPlugins::RestApiGet(study, "/series/" + seriesId + "/study", false))
396 { 398 {
423 Json::Value manifest; 425 Json::Value manifest;
424 manifest["@context"] = "http://iiif.io/api/presentation/3/context.json"; 426 manifest["@context"] = "http://iiif.io/api/presentation/3/context.json";
425 manifest["id"] = base + "/manifest.json"; 427 manifest["id"] = base + "/manifest.json";
426 manifest["type"] = "Manifest"; 428 manifest["type"] = "Manifest";
427 manifest["label"]["en"].append(study["MainDicomTags"]["StudyDate"].asString() + " - " + 429 manifest["label"]["en"].append(study["MainDicomTags"]["StudyDate"].asString() + " - " +
428 study["MainDicomTags"]["StudyDescription"].asString()); 430 series["MainDicomTags"]["Modality"].asString() + " - " +
431 study["MainDicomTags"]["StudyDescription"].asString() + " - " +
432 series["MainDicomTags"]["SeriesDescription"].asString());
429 433
430 if (sopClassUid == OrthancWSI::VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE_IOD) 434 if (sopClassUid == OrthancWSI::VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE_IOD)
431 { 435 {
432 /** 436 /**
433 * This is based on IIIF cookbook: "Support Deep Viewing with Basic 437 * This is based on IIIF cookbook: "Support Deep Viewing with Basic
440 OrthancWSI::DicomPyramidCache::Locker locker(seriesId); 444 OrthancWSI::DicomPyramidCache::Locker locker(seriesId);
441 width = locker.GetPyramid().GetLevelWidth(0); 445 width = locker.GetPyramid().GetLevelWidth(0);
442 height = locker.GetPyramid().GetLevelHeight(0); 446 height = locker.GetPyramid().GetLevelHeight(0);
443 } 447 }
444 448
445 const std::string description = (series["MainDicomTags"]["SeriesDate"].asString() + " - " + 449 AddCanvas(manifest, seriesId, "tiles/" + seriesId, 1, width, height, "");
446 series["MainDicomTags"]["SeriesDescription"].asString());
447
448 AddCanvas(manifest, seriesId, "tiles/" + seriesId, 1, width, height, description);
449 } 450 }
450 else 451 else
451 { 452 {
452 /** 453 /**
453 * This is based on IIIF cookbook: "Simple Manifest - Book" 454 * This is based on IIIF cookbook: "Simple Manifest - Book"
454 * https://iiif.io/api/cookbook/recipe/0009-book-1/ 455 * https://iiif.io/api/cookbook/recipe/0009-book-1/
455 **/ 456 **/
457
458 manifest["behavior"].append("individuals");
456 459
457 uint32_t width, height; 460 uint32_t width, height;
458 if (!oneInstance.isMember(COLUMNS) || 461 if (!oneInstance.isMember(COLUMNS) ||
459 !oneInstance.isMember(ROWS) || 462 !oneInstance.isMember(ROWS) ||
460 oneInstance[COLUMNS].type() != Json::stringValue || 463 oneInstance[COLUMNS].type() != Json::stringValue ||
496 const unsigned int start = slicesShort[instance][1].asUInt(); 499 const unsigned int start = slicesShort[instance][1].asUInt();
497 const unsigned int count = slicesShort[instance][2].asUInt(); 500 const unsigned int count = slicesShort[instance][2].asUInt();
498 501
499 for (unsigned int frame = start; frame < start + count; frame++, page++) 502 for (unsigned int frame = start; frame < start + count; frame++, page++)
500 { 503 {
501 const std::string description = "Page " + boost::lexical_cast<std::string>(page); 504 AddCanvas(manifest, instanceId, "frames/" + instanceId + "/" + boost::lexical_cast<std::string>(frame),
502 AddCanvas(manifest, instanceId, "instances/" + instanceId, page, width, height, description); 505 page, width, height, "");
503 } 506 }
504 } 507 }
505 } 508 }
506 509
507 std::string s = manifest.toStyledString(); 510 std::string s = manifest.toStyledString();
508 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), "application/json"); 511 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Json));
512 }
513
514
515 static void ServeIIIFFrameInfo(OrthancPluginRestOutput* output,
516 const char* url,
517 const OrthancPluginHttpRequest* request)
518 {
519 const std::string instanceId(request->groups[0]);
520 const std::string frame(request->groups[1]);
521
522 LOG(INFO) << "IIIF: Image API call to manifest of instance " << instanceId << " at frame " << frame;
523
524 Json::Value instance;
525 if (!OrthancPlugins::RestApiGet(instance, "/instances/" + instanceId + "/tags?short", false))
526 {
527 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
528 }
529
530 uint32_t width, height;
531 if (!instance.isMember(COLUMNS) ||
532 !instance.isMember(ROWS) ||
533 instance[COLUMNS].type() != Json::stringValue ||
534 instance[ROWS].type() != Json::stringValue ||
535 !Orthanc::SerializationToolbox::ParseFirstUnsignedInteger32(width, instance[COLUMNS].asString()) ||
536 !Orthanc::SerializationToolbox::ParseFirstUnsignedInteger32(height, instance[ROWS].asString()))
537 {
538 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
539 }
540
541 Json::Value tile;
542 tile["height"] = height;
543 tile["width"] = width;
544 tile["scaleFactors"].append(1);
545
546 Json::Value result;
547 result["@context"] = "http://iiif.io/api/image/2/context.json";
548 result["@id"] = iiifPublicUrl_ + "frames/" + instanceId + "/" + frame;
549 result["profile"] = "http://iiif.io/api/image/2/level0.json";
550 result["protocol"] = "http://iiif.io/api/image";
551 result["width"] = width;
552 result["height"] = height;
553 result["tiles"].append(tile);
554
555 std::string s = result.toStyledString();
556 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Json));
557 }
558
559
560 static void ServeIIIFFrameImage(OrthancPluginRestOutput* output,
561 const char* url,
562 const OrthancPluginHttpRequest* request)
563 {
564 const std::string instanceId(request->groups[0]);
565 const std::string frame(request->groups[1]);
566
567 LOG(INFO) << "IIIF: Image API call to JPEG of instance " << instanceId << " at frame " << frame;
568
569 std::map<std::string, std::string> httpHeaders;
570 httpHeaders["Accept"] = Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg);
571
572 std::string jpeg;
573 if (!OrthancPlugins::RestApiGetString(jpeg, "/instances/" + instanceId + "/frames/" + frame + "/preview", httpHeaders, false))
574 {
575 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
576 }
577
578 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, jpeg.empty() ? NULL : jpeg.c_str(),
579 jpeg.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg));
509 } 580 }
510 581
511 582
512 void InitializeIIIF(const std::string& iiifPublicUrl) 583 void InitializeIIIF(const std::string& iiifPublicUrl)
513 { 584 {
514 iiifPublicUrl_ = iiifPublicUrl; 585 iiifPublicUrl_ = iiifPublicUrl;
515 586
516 OrthancPlugins::RegisterRestCallback<ServeIIIFTiledImageInfo>("/wsi/iiif/tiles/([0-9a-f-]+)/info.json", true); 587 OrthancPlugins::RegisterRestCallback<ServeIIIFTiledImageInfo>("/wsi/iiif/tiles/([0-9a-f-]+)/info.json", true);
517 OrthancPlugins::RegisterRestCallback<ServeIIIFTiledImageTile>("/wsi/iiif/tiles/([0-9a-f-]+)/([0-9a-z,:]+)/([0-9a-z,!:]+)/([0-9,!]+)/([a-z]+)\\.([a-z]+)", true); 588 OrthancPlugins::RegisterRestCallback<ServeIIIFTiledImageTile>("/wsi/iiif/tiles/([0-9a-f-]+)/([0-9a-z,:]+)/([0-9a-z,!:]+)/([0-9,!]+)/([a-z]+)\\.([a-z]+)", true);
518 OrthancPlugins::RegisterRestCallback<ServeIIIFManifest>("/wsi/iiif/([0-9a-f-]+)/manifest.json", true); 589 OrthancPlugins::RegisterRestCallback<ServeIIIFManifest>("/wsi/iiif/([0-9a-f-]+)/manifest.json", true);
519 } 590 OrthancPlugins::RegisterRestCallback<ServeIIIFFrameInfo>("/wsi/iiif/frames/([0-9a-f-]+)/([0-9]+)/info.json", true);
591 OrthancPlugins::RegisterRestCallback<ServeIIIFFrameImage>("/wsi/iiif/frames/([0-9a-f-]+)/([0-9]+)/full/max/0/default.jpg", true);
592 }