Mercurial > hg > orthanc-wsi
comparison ViewerPlugin/Plugin.cpp @ 256:7deea131c3c0 iiif
implementation of IIIF Image API
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Sat, 08 Jul 2023 18:44:54 +0200 |
parents | 20a730889ae2 |
children | 9af4ba0d92fe |
comparison
equal
deleted
inserted
replaced
255:cf20f8417eaa | 256:7deea131c3c0 |
---|---|
28 #include "OrthancPluginConnection.h" | 28 #include "OrthancPluginConnection.h" |
29 | 29 |
30 #include <Compatibility.h> // For std::unique_ptr | 30 #include <Compatibility.h> // For std::unique_ptr |
31 #include <Logging.h> | 31 #include <Logging.h> |
32 #include <Images/ImageProcessing.h> | 32 #include <Images/ImageProcessing.h> |
33 #include <Images/JpegReader.h> | |
34 #include <Images/JpegWriter.h> | |
33 #include <Images/PngWriter.h> | 35 #include <Images/PngWriter.h> |
34 #include <MultiThreading/Semaphore.h> | 36 #include <MultiThreading/Semaphore.h> |
35 #include <OrthancException.h> | 37 #include <OrthancException.h> |
36 #include <SystemToolbox.h> | 38 #include <SystemToolbox.h> |
37 | 39 |
38 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" | 40 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" |
39 | 41 |
40 #include <EmbeddedResources.h> | 42 #include <EmbeddedResources.h> |
41 | 43 |
42 #include <cassert> | 44 #include <cassert> |
45 #include <boost/regex.hpp> | |
46 #include <boost/math/special_functions/round.hpp> | |
43 | 47 |
44 std::unique_ptr<OrthancWSI::OrthancPluginConnection> orthanc_; | 48 std::unique_ptr<OrthancWSI::OrthancPluginConnection> orthanc_; |
45 std::unique_ptr<OrthancWSI::DicomPyramidCache> cache_; | 49 std::unique_ptr<OrthancWSI::DicomPyramidCache> cache_; |
46 std::unique_ptr<Orthanc::Semaphore> transcoderSemaphore_; | 50 std::unique_ptr<Orthanc::Semaphore> transcoderSemaphore_; |
47 | 51 static std::string publicIIIFUrl_; |
48 | 52 |
49 static void AnswerSparseTile(OrthancPluginRestOutput* output, | 53 static void AnswerSparseTile(OrthancPluginRestOutput* output, |
50 unsigned int tileWidth, | 54 unsigned int tileWidth, |
51 unsigned int tileHeight) | 55 unsigned int tileHeight) |
52 { | 56 { |
132 std::string s = result.toStyledString(); | 136 std::string s = result.toStyledString(); |
133 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), "application/json"); | 137 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), "application/json"); |
134 } | 138 } |
135 | 139 |
136 | 140 |
141 class RawTile : public boost::noncopyable | |
142 { | |
143 private: | |
144 Orthanc::PixelFormat format_; | |
145 unsigned int tileWidth_; | |
146 unsigned int tileHeight_; | |
147 Orthanc::PhotometricInterpretation photometric_; | |
148 std::string tile_; | |
149 OrthancWSI::ImageCompression compression_; | |
150 | |
151 Orthanc::ImageAccessor* DecodeInternal() | |
152 { | |
153 switch (compression_) | |
154 { | |
155 case OrthancWSI::ImageCompression_Jpeg: | |
156 { | |
157 std::unique_ptr<Orthanc::JpegReader> decoded(new Orthanc::JpegReader); | |
158 decoded->ReadFromMemory(tile_); | |
159 return decoded.release(); | |
160 } | |
161 | |
162 case OrthancWSI::ImageCompression_Jpeg2000: | |
163 { | |
164 std::unique_ptr<OrthancWSI::Jpeg2000Reader> decoded(new OrthancWSI::Jpeg2000Reader); | |
165 decoded->ReadFromMemory(tile_); | |
166 | |
167 if (photometric_ == Orthanc::PhotometricInterpretation_YBR_ICT) | |
168 { | |
169 OrthancWSI::ImageToolbox::ConvertJpegYCbCrToRgb(*decoded); | |
170 } | |
171 | |
172 return decoded.release(); | |
173 } | |
174 | |
175 case OrthancWSI::ImageCompression_None: | |
176 { | |
177 unsigned int bpp = Orthanc::GetBytesPerPixel(format_); | |
178 if (bpp * tileWidth_ * tileHeight_ != tile_.size()) | |
179 { | |
180 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
181 } | |
182 | |
183 std::unique_ptr<Orthanc::ImageAccessor> decoded(new Orthanc::ImageAccessor); | |
184 decoded->AssignReadOnly(format_, tileWidth_, tileHeight_, bpp * tileWidth_, tile_.c_str()); | |
185 | |
186 return decoded.release(); | |
187 } | |
188 | |
189 default: | |
190 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
191 } | |
192 } | |
193 | |
194 static void EncodeInternal(std::string& encoded, | |
195 const Orthanc::ImageAccessor& decoded, | |
196 Orthanc::MimeType transcodingType) | |
197 { | |
198 switch (transcodingType) | |
199 { | |
200 case Orthanc::MimeType_Png: | |
201 { | |
202 Orthanc::PngWriter writer; | |
203 Orthanc::IImageWriter::WriteToMemory(writer, encoded, decoded); | |
204 break; | |
205 } | |
206 | |
207 case Orthanc::MimeType_Jpeg: | |
208 { | |
209 Orthanc::JpegWriter writer; | |
210 Orthanc::IImageWriter::WriteToMemory(writer, encoded, decoded); | |
211 break; | |
212 } | |
213 | |
214 default: | |
215 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
216 } | |
217 } | |
218 | |
219 | |
220 public: | |
221 RawTile(OrthancWSI::ITiledPyramid& pyramid, | |
222 unsigned int level, | |
223 unsigned int tileX, | |
224 unsigned int tileY) : | |
225 format_(pyramid.GetPixelFormat()), | |
226 tileWidth_(pyramid.GetTileWidth(level)), | |
227 tileHeight_(pyramid.GetTileHeight(level)), | |
228 photometric_(pyramid.GetPhotometricInterpretation()) | |
229 { | |
230 if (!pyramid.ReadRawTile(tile_, compression_, level, tileX, tileY)) | |
231 { | |
232 // Handling of missing tile (for sparse tiling): TODO parameter? | |
233 // AnswerSparseTile(output, tileWidth, tileHeight); return; | |
234 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); | |
235 } | |
236 } | |
237 | |
238 void Answer(OrthancPluginRestOutput* output, | |
239 Orthanc::MimeType transcodingType) | |
240 { | |
241 if (compression_ == OrthancWSI::ImageCompression_Jpeg) | |
242 { | |
243 // The tile is already a JPEG image. In such a case, we can | |
244 // serve it as such, because any Web browser can handle JPEG. | |
245 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, tile_.c_str(), | |
246 tile_.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)); | |
247 } | |
248 else | |
249 { | |
250 // This is a lossless frame (coming from a JPEG2000 or an | |
251 // uncompressed DICOM instance), which is not a DICOM-JPEG | |
252 // instance. We need to decompress the raw tile, then transcode | |
253 // it to the PNG/JPEG, depending on the "transcodingType". | |
254 | |
255 std::string transcoded; | |
256 | |
257 { | |
258 // The semaphore is used to throttle the number of simultaneous computations | |
259 Orthanc::Semaphore::Locker locker(*transcoderSemaphore_); | |
260 | |
261 std::unique_ptr<Orthanc::ImageAccessor> decoded(DecodeInternal()); | |
262 EncodeInternal(transcoded, *decoded, transcodingType); | |
263 } | |
264 | |
265 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, transcoded.c_str(), | |
266 transcoded.size(), Orthanc::EnumerationToString(transcodingType)); | |
267 } | |
268 } | |
269 | |
270 Orthanc::ImageAccessor* Decode() | |
271 { | |
272 Orthanc::Semaphore::Locker locker(*transcoderSemaphore_); | |
273 return DecodeInternal(); | |
274 } | |
275 | |
276 static void Encode(std::string& encoded, | |
277 const Orthanc::ImageAccessor& decoded, | |
278 Orthanc::MimeType transcodingType) | |
279 { | |
280 Orthanc::Semaphore::Locker locker(*transcoderSemaphore_); | |
281 EncodeInternal(encoded, decoded, transcodingType); | |
282 } | |
283 }; | |
284 | |
285 | |
137 void ServeTile(OrthancPluginRestOutput* output, | 286 void ServeTile(OrthancPluginRestOutput* output, |
138 const char* url, | 287 const char* url, |
139 const OrthancPluginHttpRequest* request) | 288 const OrthancPluginHttpRequest* request) |
140 { | 289 { |
141 std::string seriesId(request->groups[0]); | 290 std::string seriesId(request->groups[0]); |
153 { | 302 { |
154 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | 303 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); |
155 } | 304 } |
156 | 305 |
157 // Retrieve the raw tile from the WSI pyramid | 306 // Retrieve the raw tile from the WSI pyramid |
158 OrthancWSI::ImageCompression compression; | 307 std::unique_ptr<RawTile> rawTile; |
159 Orthanc::PhotometricInterpretation photometric; | |
160 Orthanc::PixelFormat format; | |
161 std::string tile; | |
162 unsigned int tileWidth, tileHeight; | |
163 | 308 |
164 { | 309 { |
165 OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId); | 310 OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId); |
166 | 311 rawTile.reset(new RawTile(locker.GetPyramid(), |
167 format = locker.GetPyramid().GetPixelFormat(); | 312 static_cast<unsigned int>(level), |
168 tileWidth = locker.GetPyramid().GetTileWidth(level); | 313 static_cast<unsigned int>(tileX), |
169 tileHeight = locker.GetPyramid().GetTileHeight(level); | 314 static_cast<unsigned int>(tileY))); |
170 photometric = locker.GetPyramid().GetPhotometricInterpretation(); | 315 } |
171 | 316 |
172 if (!locker.GetPyramid().ReadRawTile(tile, compression, | 317 /** |
173 static_cast<unsigned int>(level), | 318 * In the case the DICOM file doesn't use the JPEG transfer syntax, |
174 static_cast<unsigned int>(tileX), | 319 * transfer the tile (which is presumably lossless) as a PNG image |
175 static_cast<unsigned int>(tileY))) | 320 * so as to prevent lossy compression. Don't call "rawTile" while |
176 { | 321 * the Locker is around, as "Answer()" can be a costly operation. |
177 // Handling of missing tile (for sparse tiling): TODO parameter? | 322 **/ |
178 // AnswerSparseTile(output, tileWidth, tileHeight); return; | 323 rawTile->Answer(output, Orthanc::MimeType_Png); |
179 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); | |
180 } | |
181 } | |
182 | |
183 | |
184 // Test whether the tile is a JPEG image. In such a case, we can | |
185 // serve it as such, because any Web browser can handle JPEG | |
186 | |
187 if (compression == OrthancWSI::ImageCompression_Jpeg) | |
188 { | |
189 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, tile.c_str(), tile.size(), "image/jpeg"); | |
190 return; // We're done | |
191 } | |
192 | |
193 | |
194 // The tile does not come from a DICOM-JPEG instance, we need to | |
195 // decompress the raw tile | |
196 std::unique_ptr<Orthanc::ImageAccessor> decoded; | |
197 | |
198 Orthanc::Semaphore::Locker locker(*transcoderSemaphore_); | |
199 | |
200 switch (compression) | |
201 { | |
202 case OrthancWSI::ImageCompression_Jpeg2000: | |
203 decoded.reset(new OrthancWSI::Jpeg2000Reader); | |
204 dynamic_cast<OrthancWSI::Jpeg2000Reader&>(*decoded).ReadFromMemory(tile); | |
205 | |
206 if (photometric == Orthanc::PhotometricInterpretation_YBR_ICT) | |
207 { | |
208 OrthancWSI::ImageToolbox::ConvertJpegYCbCrToRgb(*decoded); | |
209 } | |
210 | |
211 break; | |
212 | |
213 case OrthancWSI::ImageCompression_None: | |
214 { | |
215 unsigned int bpp = Orthanc::GetBytesPerPixel(format); | |
216 if (bpp * tileWidth * tileHeight != tile.size()) | |
217 { | |
218 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
219 } | |
220 | |
221 decoded.reset(new Orthanc::ImageAccessor); | |
222 decoded->AssignReadOnly(format, tileWidth, tileHeight, bpp * tileWidth, tile.c_str()); | |
223 break; | |
224 } | |
225 | |
226 default: | |
227 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
228 } | |
229 | |
230 | |
231 // This is a lossless frame (coming from a JPEG2000 or uncompressed | |
232 // DICOM instance), serve it as a PNG image so as to prevent lossy | |
233 // compression | |
234 | |
235 std::string png; | |
236 Orthanc::PngWriter writer; | |
237 Orthanc::IImageWriter::WriteToMemory(writer, png, *decoded); | |
238 | |
239 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, png.c_str(), png.size(), "image/png"); | |
240 } | 324 } |
241 | 325 |
242 | 326 |
243 OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, | 327 OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, |
244 OrthancPluginResourceType resourceType, | 328 OrthancPluginResourceType resourceType, |
297 Orthanc::EmbeddedResources::GetFileResource(content, resource); | 381 Orthanc::EmbeddedResources::GetFileResource(content, resource); |
298 | 382 |
299 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, content.c_str(), content.size(), mime.c_str()); | 383 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, content.c_str(), content.size(), mime.c_str()); |
300 } | 384 } |
301 | 385 |
386 | |
387 | |
388 void ServeIIIFImageInfo(OrthancPluginRestOutput* output, | |
389 const char* url, | |
390 const OrthancPluginHttpRequest* request) | |
391 { | |
392 std::string seriesId(request->groups[0]); | |
393 | |
394 LOG(INFO) << "IIIF: Accessing whole-slide pyramid of series " << seriesId; | |
395 | |
396 OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId); | |
397 | |
398 if (locker.GetPyramid().GetLevelCount() == 0) | |
399 { | |
400 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
401 } | |
402 | |
403 if (locker.GetPyramid().GetTileWidth(0) != locker.GetPyramid().GetTileHeight(0)) | |
404 { | |
405 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat, | |
406 "IIIF doesn't support non-isotropic tile sizes"); | |
407 } | |
408 | |
409 for (unsigned int i = 1; i < locker.GetPyramid().GetLevelCount(); i++) | |
410 { | |
411 if (locker.GetPyramid().GetTileWidth(i) != locker.GetPyramid().GetTileWidth(0) || | |
412 locker.GetPyramid().GetTileHeight(i) != locker.GetPyramid().GetTileHeight(0)) | |
413 { | |
414 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat, | |
415 "IIIF doesn't support levels with varying tile sizes"); | |
416 } | |
417 } | |
418 | |
419 Json::Value sizes = Json::arrayValue; | |
420 for (unsigned int i = locker.GetPyramid().GetLevelCount(); i > 0; i--) | |
421 { | |
422 Json::Value level; | |
423 level["width"] = locker.GetPyramid().GetLevelWidth(i - 1); | |
424 level["height"] = locker.GetPyramid().GetLevelHeight(i - 1); | |
425 sizes.append(level); | |
426 } | |
427 | |
428 Json::Value scaleFactors = Json::arrayValue; | |
429 for (unsigned int i = locker.GetPyramid().GetLevelCount(); i > 0; i--) | |
430 { | |
431 scaleFactors.append(static_cast<float>(locker.GetPyramid().GetLevelWidth(0)) / | |
432 static_cast<float>(locker.GetPyramid().GetLevelWidth(i - 1))); | |
433 } | |
434 | |
435 Json::Value tiles; | |
436 tiles["width"] = locker.GetPyramid().GetTileWidth(0); | |
437 tiles["height"] = locker.GetPyramid().GetTileHeight(0); | |
438 tiles["scaleFactors"] = scaleFactors; | |
439 | |
440 Json::Value result; | |
441 result["@context"] = "http://iiif.io/api/image/2/context.json"; | |
442 result["@id"] = publicIIIFUrl_ + seriesId; | |
443 result["profile"] = "http://iiif.io/api/image/2/level0.json"; | |
444 result["protocol"] = "http://iiif.io/api/image"; | |
445 result["width"] = locker.GetPyramid().GetLevelWidth(0); | |
446 result["height"] = locker.GetPyramid().GetLevelHeight(0); | |
447 result["sizes"] = sizes; | |
448 | |
449 result["tiles"] = Json::arrayValue; | |
450 result["tiles"].append(tiles); | |
451 | |
452 std::string s = result.toStyledString(); | |
453 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), "application/json"); | |
454 } | |
455 | |
456 | |
457 static unsigned int GetPhysicalTileWidth(const OrthancWSI::ITiledPyramid& pyramid, | |
458 unsigned int level) | |
459 { | |
460 return static_cast<unsigned int>(boost::math::iround( | |
461 static_cast<float>(pyramid.GetTileWidth(level)) * | |
462 static_cast<float>(pyramid.GetLevelWidth(0)) / | |
463 static_cast<float>(pyramid.GetLevelWidth(level)))); | |
464 } | |
465 | |
466 | |
467 static unsigned int GetPhysicalTileHeight(const OrthancWSI::ITiledPyramid& pyramid, | |
468 unsigned int level) | |
469 { | |
470 return static_cast<unsigned int>(boost::math::iround( | |
471 static_cast<float>(pyramid.GetTileHeight(level)) * | |
472 static_cast<float>(pyramid.GetLevelHeight(0)) / | |
473 static_cast<float>(pyramid.GetLevelHeight(level)))); | |
474 } | |
475 | |
476 | |
477 void ServeIIIFImageTile(OrthancPluginRestOutput* output, | |
478 const char* url, | |
479 const OrthancPluginHttpRequest* request) | |
480 { | |
481 std::string seriesId(request->groups[0]); | |
482 std::string region(request->groups[1]); | |
483 std::string size(request->groups[2]); | |
484 std::string rotation(request->groups[3]); | |
485 std::string quality(request->groups[4]); | |
486 std::string format(request->groups[5]); | |
487 | |
488 LOG(INFO) << "IIIF: Accessing tile of series " << seriesId << ": " | |
489 << "region=" << region << "; size=" << size << "; rotation=" | |
490 << rotation << "; quality=" << quality << "; format=" << format; | |
491 | |
492 if (region == "full") | |
493 { | |
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); | |
565 | |
566 OrthancWSI::ITiledPyramid& pyramid = locker.GetPyramid(); | |
567 | |
568 unsigned int level; | |
569 for (level = 0; level < pyramid.GetLevelCount(); level++) | |
570 { | |
571 const unsigned int physicalTileWidth = GetPhysicalTileWidth(pyramid, level); | |
572 const unsigned int physicalTileHeight = GetPhysicalTileHeight(pyramid, level); | |
573 | |
574 if (regionX % physicalTileWidth == 0 && | |
575 regionY % physicalTileHeight == 0 && | |
576 regionWidth <= physicalTileWidth && | |
577 regionHeight <= physicalTileHeight) | |
578 { | |
579 break; | |
580 } | |
581 } | |
582 | |
583 if (cropWidth > pyramid.GetTileWidth(level)) | |
584 { | |
585 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "IIIF - Request for a cropping that is too large for the tile size"); | |
586 } | |
587 | |
588 if (level == pyramid.GetLevelCount()) | |
589 { | |
590 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "IIIF - Cannot locate the level of interest"); | |
591 } | |
592 else | |
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 | |
621 std::string encoded; | |
622 RawTile::Encode(encoded, cropped, Orthanc::MimeType_Jpeg); | |
623 | |
624 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, encoded.c_str(), | |
625 encoded.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)); | |
626 } | |
627 else | |
628 { | |
629 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
630 } | |
631 } | |
302 | 632 |
303 | 633 |
304 extern "C" | 634 extern "C" |
305 { | 635 { |
306 ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) | 636 ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) |
346 OrthancPluginSetDescription(context, "Provides a Web viewer of whole-slide microscopic images within Orthanc."); | 676 OrthancPluginSetDescription(context, "Provides a Web viewer of whole-slide microscopic images within Orthanc."); |
347 | 677 |
348 orthanc_.reset(new OrthancWSI::OrthancPluginConnection); | 678 orthanc_.reset(new OrthancWSI::OrthancPluginConnection); |
349 cache_.reset(new OrthancWSI::DicomPyramidCache(*orthanc_, 10 /* Number of pyramids to be cached - TODO parameter */)); | 679 cache_.reset(new OrthancWSI::DicomPyramidCache(*orthanc_, 10 /* Number of pyramids to be cached - TODO parameter */)); |
350 | 680 |
681 { | |
682 // TODO => CONFIG | |
683 publicIIIFUrl_ = "http://localhost:8042/wsi/iiif"; | |
684 | |
685 if (publicIIIFUrl_.empty() || | |
686 publicIIIFUrl_[publicIIIFUrl_.size() - 1] != '/') | |
687 { | |
688 publicIIIFUrl_ += "/"; | |
689 } | |
690 } | |
691 | |
351 OrthancPluginRegisterOnChangeCallback(OrthancPlugins::GetGlobalContext(), OnChangeCallback); | 692 OrthancPluginRegisterOnChangeCallback(OrthancPlugins::GetGlobalContext(), OnChangeCallback); |
352 | 693 |
353 OrthancPlugins::RegisterRestCallback<ServeFile>("/wsi/app/(ol.css)", true); | 694 OrthancPlugins::RegisterRestCallback<ServeFile>("/wsi/app/(ol.css)", true); |
354 OrthancPlugins::RegisterRestCallback<ServeFile>("/wsi/app/(ol.js)", true); | 695 OrthancPlugins::RegisterRestCallback<ServeFile>("/wsi/app/(ol.js)", true); |
355 OrthancPlugins::RegisterRestCallback<ServeFile>("/wsi/app/(viewer.html)", true); | 696 OrthancPlugins::RegisterRestCallback<ServeFile>("/wsi/app/(viewer.html)", true); |
356 OrthancPlugins::RegisterRestCallback<ServeFile>("/wsi/app/(viewer.js)", true); | 697 OrthancPlugins::RegisterRestCallback<ServeFile>("/wsi/app/(viewer.js)", true); |
357 OrthancPlugins::RegisterRestCallback<ServePyramid>("/wsi/pyramids/([0-9a-f-]+)", true); | 698 OrthancPlugins::RegisterRestCallback<ServePyramid>("/wsi/pyramids/([0-9a-f-]+)", true); |
358 OrthancPlugins::RegisterRestCallback<ServeTile>("/wsi/tiles/([0-9a-f-]+)/([0-9-]+)/([0-9-]+)/([0-9-]+)", true); | 699 OrthancPlugins::RegisterRestCallback<ServeTile>("/wsi/tiles/([0-9a-f-]+)/([0-9-]+)/([0-9-]+)/([0-9-]+)", true); |
359 | 700 |
701 OrthancPlugins::RegisterRestCallback<ServeIIIFImageInfo>("/wsi/iiif/([0-9a-f-]+)/info.json", true); | |
702 OrthancPlugins::RegisterRestCallback<ServeIIIFImageTile>("/wsi/iiif/([0-9a-f-]+)/([0-9a-z,:]+)/([0-9a-z,!:]+)/([0-9,!]+)/([a-z]+)\\.([a-z]+)", true); | |
703 | |
360 // Extend the default Orthanc Explorer with custom JavaScript for WSI | 704 // Extend the default Orthanc Explorer with custom JavaScript for WSI |
361 std::string explorer; | 705 std::string explorer; |
362 Orthanc::EmbeddedResources::GetFileResource(explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER); | 706 Orthanc::EmbeddedResources::GetFileResource(explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER); |
363 OrthancPluginExtendOrthancExplorer(OrthancPlugins::GetGlobalContext(), explorer.c_str()); | 707 OrthancPluginExtendOrthancExplorer(OrthancPlugins::GetGlobalContext(), explorer.c_str()); |
364 | 708 |