Mercurial > hg > orthanc-wsi
comparison ViewerPlugin/Plugin.cpp @ 257:9af4ba0d92fe iiif
support of full rendering in IIIF
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Sun, 09 Jul 2023 15:24:37 +0200 |
parents | 7deea131c3c0 |
children | 07be799fa383 |
comparison
equal
deleted
inserted
replaced
256:7deea131c3c0 | 257:9af4ba0d92fe |
---|---|
487 | 487 |
488 LOG(INFO) << "IIIF: Accessing tile of series " << seriesId << ": " | 488 LOG(INFO) << "IIIF: Accessing tile of series " << seriesId << ": " |
489 << "region=" << region << "; size=" << size << "; rotation=" | 489 << "region=" << region << "; size=" << size << "; rotation=" |
490 << rotation << "; quality=" << quality << "; format=" << format; | 490 << rotation << "; quality=" << quality << "; format=" << format; |
491 | 491 |
492 if (rotation != "0") | |
493 { | |
494 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Unsupported rotation: " + rotation); | |
495 } | |
496 | |
497 if (quality != "default") | |
498 { | |
499 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Unsupported quality: " + quality); | |
500 } | |
501 | |
502 if (format != "jpg") | |
503 { | |
504 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Unsupported format: " + format); | |
505 } | |
506 | |
492 if (region == "full") | 507 if (region == "full") |
493 { | 508 { |
494 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Full region is not supported for whole-slide images"); | |
495 } | |
496 | |
497 if (rotation != "0") | |
498 { | |
499 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Unsupported rotation: " + rotation); | |
500 } | |
501 | |
502 if (quality != "default") | |
503 { | |
504 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Unsupported quality: " + quality); | |
505 } | |
506 | |
507 if (format != "jpg") | |
508 { | |
509 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Unsupported format: " + format); | |
510 } | |
511 | |
512 int regionX, regionY, regionWidth, regionHeight; | |
513 | |
514 bool ok = false; | |
515 boost::regex regionPattern("([0-9]+),([0-9]+),([0-9]+),([0-9]+)"); | |
516 boost::cmatch regionWhat; | |
517 if (regex_match(region.c_str(), regionWhat, regionPattern)) | |
518 { | |
519 try | |
520 { | |
521 regionX = boost::lexical_cast<int>(regionWhat[1]); | |
522 regionY = boost::lexical_cast<int>(regionWhat[2]); | |
523 regionWidth = boost::lexical_cast<int>(regionWhat[3]); | |
524 regionHeight = boost::lexical_cast<int>(regionWhat[4]); | |
525 ok = (regionX >= 0 && | |
526 regionY >= 0 && | |
527 regionWidth > 0 && | |
528 regionHeight > 0); | |
529 } | |
530 catch (boost::bad_lexical_cast&) | |
531 { | |
532 } | |
533 } | |
534 | |
535 if (!ok) | |
536 { | |
537 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Not a (x,y,width,height) region: " + region); | |
538 } | |
539 | |
540 int cropWidth; | |
541 boost::regex sizePattern("([0-9]+),"); | |
542 boost::cmatch sizeWhat; | |
543 if (regex_match(size.c_str(), sizeWhat, sizePattern)) | |
544 { | |
545 try | |
546 { | |
547 cropWidth = boost::lexical_cast<int>(sizeWhat[1]); | |
548 ok = (cropWidth > 0); | |
549 } | |
550 catch (boost::bad_lexical_cast&) | |
551 { | |
552 } | |
553 } | |
554 | |
555 if (!ok) | |
556 { | |
557 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Not a (width,) size: " + size); | |
558 } | |
559 | |
560 std::unique_ptr<RawTile> rawTile; | |
561 std::unique_ptr<Orthanc::ImageAccessor> toCrop; | |
562 | |
563 { | |
564 OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId); | 509 OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId); |
565 | 510 |
566 OrthancWSI::ITiledPyramid& pyramid = locker.GetPyramid(); | 511 OrthancWSI::ITiledPyramid& pyramid = locker.GetPyramid(); |
567 | 512 const unsigned int level = pyramid.GetLevelCount() - 1; |
568 unsigned int level; | 513 |
569 for (level = 0; level < pyramid.GetLevelCount(); level++) | 514 Orthanc::Image full(Orthanc::PixelFormat_RGB24, pyramid.GetLevelWidth(level), pyramid.GetLevelHeight(level), false); |
570 { | 515 Orthanc::ImageProcessing::Set(full, 255, 255, 255, 0); |
571 const unsigned int physicalTileWidth = GetPhysicalTileWidth(pyramid, level); | 516 |
572 const unsigned int physicalTileHeight = GetPhysicalTileHeight(pyramid, level); | 517 const unsigned int nx = OrthancWSI::CeilingDivision(pyramid.GetLevelWidth(level), pyramid.GetTileWidth(level)); |
573 | 518 const unsigned int ny = OrthancWSI::CeilingDivision(pyramid.GetLevelHeight(level), pyramid.GetTileHeight(level)); |
574 if (regionX % physicalTileWidth == 0 && | 519 for (unsigned int ty = 0; ty < ny; ty++) |
575 regionY % physicalTileHeight == 0 && | 520 { |
576 regionWidth <= physicalTileWidth && | 521 const unsigned int y = ty * pyramid.GetTileHeight(level); |
577 regionHeight <= physicalTileHeight) | 522 const unsigned int height = std::min(pyramid.GetTileHeight(level), full.GetHeight() - y); |
578 { | 523 |
579 break; | 524 for (unsigned int tx = 0; tx < nx; tx++) |
580 } | 525 { |
581 } | 526 const unsigned int x = tx * pyramid.GetTileWidth(level); |
582 | 527 std::unique_ptr<Orthanc::ImageAccessor> tile(pyramid.DecodeTile(level, tx, ty)); |
583 if (cropWidth > pyramid.GetTileWidth(level)) | 528 |
584 { | 529 const unsigned int width = std::min(pyramid.GetTileWidth(level), full.GetWidth() - x); |
585 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "IIIF - Request for a cropping that is too large for the tile size"); | 530 |
586 } | 531 Orthanc::ImageAccessor source, target; |
587 | 532 tile->GetRegion(source, 0, 0, width, height); |
588 if (level == pyramid.GetLevelCount()) | 533 full.GetRegion(target, x, y, width, height); |
589 { | 534 |
590 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "IIIF - Cannot locate the level of interest"); | 535 Orthanc::ImageProcessing::Copy(target, source); |
591 } | 536 } |
592 else | 537 } |
593 { | |
594 rawTile.reset(new RawTile(locker.GetPyramid(), level, | |
595 regionX / GetPhysicalTileWidth(pyramid, level), | |
596 regionY / GetPhysicalTileHeight(pyramid, level))); | |
597 | |
598 if (cropWidth < pyramid.GetTileWidth(level)) | |
599 { | |
600 toCrop.reset(rawTile->Decode()); | |
601 rawTile.reset(NULL); | |
602 } | |
603 } | |
604 } | |
605 | |
606 if (rawTile.get() != NULL) | |
607 { | |
608 assert(toCrop.get() == NULL); | |
609 | |
610 // Level 0 Compliance of IIIF expects JPEG files | |
611 rawTile->Answer(output, Orthanc::MimeType_Jpeg); | |
612 } | |
613 else if (toCrop.get() != NULL) | |
614 { | |
615 assert(rawTile.get() == NULL); | |
616 assert(cropWidth < toCrop->GetWidth()); | |
617 | |
618 Orthanc::ImageAccessor cropped; | |
619 toCrop->GetRegion(cropped, 0, 0, cropWidth, toCrop->GetHeight()); | |
620 | 538 |
621 std::string encoded; | 539 std::string encoded; |
622 RawTile::Encode(encoded, cropped, Orthanc::MimeType_Jpeg); | 540 RawTile::Encode(encoded, full, Orthanc::MimeType_Jpeg); |
623 | 541 |
624 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, encoded.c_str(), | 542 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, encoded.c_str(), |
625 encoded.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)); | 543 encoded.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)); |
626 } | 544 } |
627 else | 545 else |
628 { | 546 { |
629 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | 547 int regionX, regionY, regionWidth, regionHeight; |
548 | |
549 bool ok = false; | |
550 boost::regex regionPattern("([0-9]+),([0-9]+),([0-9]+),([0-9]+)"); | |
551 boost::cmatch regionWhat; | |
552 if (regex_match(region.c_str(), regionWhat, regionPattern)) | |
553 { | |
554 try | |
555 { | |
556 regionX = boost::lexical_cast<int>(regionWhat[1]); | |
557 regionY = boost::lexical_cast<int>(regionWhat[2]); | |
558 regionWidth = boost::lexical_cast<int>(regionWhat[3]); | |
559 regionHeight = boost::lexical_cast<int>(regionWhat[4]); | |
560 ok = (regionX >= 0 && | |
561 regionY >= 0 && | |
562 regionWidth > 0 && | |
563 regionHeight > 0); | |
564 } | |
565 catch (boost::bad_lexical_cast&) | |
566 { | |
567 } | |
568 } | |
569 | |
570 if (!ok) | |
571 { | |
572 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Not a (x,y,width,height) region: " + region); | |
573 } | |
574 | |
575 int cropWidth; | |
576 boost::regex sizePattern("([0-9]+),"); | |
577 boost::cmatch sizeWhat; | |
578 if (regex_match(size.c_str(), sizeWhat, sizePattern)) | |
579 { | |
580 try | |
581 { | |
582 cropWidth = boost::lexical_cast<int>(sizeWhat[1]); | |
583 ok = (cropWidth > 0); | |
584 } | |
585 catch (boost::bad_lexical_cast&) | |
586 { | |
587 } | |
588 } | |
589 | |
590 if (!ok) | |
591 { | |
592 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Not a (width,) size: " + size); | |
593 } | |
594 | |
595 std::unique_ptr<RawTile> rawTile; | |
596 std::unique_ptr<Orthanc::ImageAccessor> toCrop; | |
597 | |
598 { | |
599 OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId); | |
600 | |
601 OrthancWSI::ITiledPyramid& pyramid = locker.GetPyramid(); | |
602 | |
603 unsigned int level; | |
604 for (level = 0; level < pyramid.GetLevelCount(); level++) | |
605 { | |
606 const unsigned int physicalTileWidth = GetPhysicalTileWidth(pyramid, level); | |
607 const unsigned int physicalTileHeight = GetPhysicalTileHeight(pyramid, level); | |
608 | |
609 if (regionX % physicalTileWidth == 0 && | |
610 regionY % physicalTileHeight == 0 && | |
611 regionWidth <= physicalTileWidth && | |
612 regionHeight <= physicalTileHeight) | |
613 { | |
614 break; | |
615 } | |
616 } | |
617 | |
618 if (cropWidth > pyramid.GetTileWidth(level)) | |
619 { | |
620 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "IIIF - Request for a cropping that is too large for the tile size"); | |
621 } | |
622 | |
623 if (level == pyramid.GetLevelCount()) | |
624 { | |
625 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "IIIF - Cannot locate the level of interest"); | |
626 } | |
627 else | |
628 { | |
629 rawTile.reset(new RawTile(locker.GetPyramid(), level, | |
630 regionX / GetPhysicalTileWidth(pyramid, level), | |
631 regionY / GetPhysicalTileHeight(pyramid, level))); | |
632 | |
633 if (cropWidth < pyramid.GetTileWidth(level)) | |
634 { | |
635 toCrop.reset(rawTile->Decode()); | |
636 rawTile.reset(NULL); | |
637 } | |
638 } | |
639 } | |
640 | |
641 if (rawTile.get() != NULL) | |
642 { | |
643 assert(toCrop.get() == NULL); | |
644 | |
645 // Level 0 Compliance of IIIF expects JPEG files | |
646 rawTile->Answer(output, Orthanc::MimeType_Jpeg); | |
647 } | |
648 else if (toCrop.get() != NULL) | |
649 { | |
650 assert(rawTile.get() == NULL); | |
651 assert(cropWidth < toCrop->GetWidth()); | |
652 | |
653 Orthanc::ImageAccessor cropped; | |
654 toCrop->GetRegion(cropped, 0, 0, cropWidth, toCrop->GetHeight()); | |
655 | |
656 std::string encoded; | |
657 RawTile::Encode(encoded, cropped, Orthanc::MimeType_Jpeg); | |
658 | |
659 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, encoded.c_str(), | |
660 encoded.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)); | |
661 } | |
662 else | |
663 { | |
664 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
665 } | |
630 } | 666 } |
631 } | 667 } |
632 | 668 |
633 | 669 |
634 extern "C" | 670 extern "C" |