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