Mercurial > hg > orthanc
comparison OrthancServer/Internals/DicomImageDecoder.cpp @ 1826:ac5b0b4e2434
refactoring of DicomImageDecoder
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 25 Nov 2015 16:00:57 +0100 |
parents | b530c3dfe2a6 |
children | b1291df2f780 |
comparison
equal
deleted
inserted
replaced
1825:f0f8a94c0858 | 1826:ac5b0b4e2434 |
---|---|
80 #include "../PrecompiledHeadersServer.h" | 80 #include "../PrecompiledHeadersServer.h" |
81 #include "DicomImageDecoder.h" | 81 #include "DicomImageDecoder.h" |
82 | 82 |
83 #include "../../Core/Logging.h" | 83 #include "../../Core/Logging.h" |
84 #include "../../Core/OrthancException.h" | 84 #include "../../Core/OrthancException.h" |
85 #include "../../Core/Images/Image.h" | |
85 #include "../../Core/Images/ImageProcessing.h" | 86 #include "../../Core/Images/ImageProcessing.h" |
86 #include "../../Core/Images/PngWriter.h" // TODO REMOVE THIS | |
87 #include "../../Core/DicomFormat/DicomIntegerPixelAccessor.h" | 87 #include "../../Core/DicomFormat/DicomIntegerPixelAccessor.h" |
88 #include "../ToDcmtkBridge.h" | 88 #include "../ToDcmtkBridge.h" |
89 #include "../FromDcmtkBridge.h" | 89 #include "../FromDcmtkBridge.h" |
90 #include "../ParsedDicomFile.h" | 90 #include "../ParsedDicomFile.h" |
91 | 91 |
92 #include <boost/lexical_cast.hpp> | 92 #include <boost/lexical_cast.hpp> |
93 | |
94 #include <dcmtk/dcmdata/dcfilefo.h> | |
93 | 95 |
94 #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 | 96 #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 |
95 #include <dcmtk/dcmjpls/djcodecd.h> | 97 #include <dcmtk/dcmjpls/djcodecd.h> |
96 #include <dcmtk/dcmjpls/djcparam.h> | 98 #include <dcmtk/dcmjpls/djcparam.h> |
97 #include <dcmtk/dcmjpeg/djrplol.h> | 99 #include <dcmtk/dcmjpeg/djrplol.h> |
302 return slowAccessor_->GetSize(); | 304 return slowAccessor_->GetSize(); |
303 } | 305 } |
304 }; | 306 }; |
305 | 307 |
306 | 308 |
307 void DicomImageDecoder::SetupImageBuffer(ImageBuffer& target, | 309 ImageAccessor* DicomImageDecoder::CreateImage(DcmDataset& dataset) |
308 DcmDataset& dataset) | |
309 { | 310 { |
310 DicomMap m; | 311 DicomMap m; |
311 FromDcmtkBridge::Convert(m, dataset); | 312 FromDcmtkBridge::Convert(m, dataset); |
312 | 313 |
313 DicomImageInformation info(m); | 314 DicomImageInformation info(m); |
322 << EnumerationToString(info.GetPhotometricInterpretation()) | 323 << EnumerationToString(info.GetPhotometricInterpretation()) |
323 << " photometric interpretation"; | 324 << " photometric interpretation"; |
324 throw OrthancException(ErrorCode_NotImplemented); | 325 throw OrthancException(ErrorCode_NotImplemented); |
325 } | 326 } |
326 | 327 |
327 target.SetHeight(info.GetHeight()); | 328 return new Image(format, info.GetWidth(), info.GetHeight()); |
328 target.SetWidth(info.GetWidth()); | |
329 target.SetFormat(format); | |
330 } | 329 } |
331 | 330 |
332 | 331 |
333 bool DicomImageDecoder::IsUncompressedImage(const DcmDataset& dataset) | 332 bool DicomImageDecoder::IsUncompressedImage(const DcmDataset& dataset) |
334 { | 333 { |
372 } | 371 } |
373 } | 372 } |
374 } | 373 } |
375 | 374 |
376 | 375 |
377 void DicomImageDecoder::DecodeUncompressedImage(ImageBuffer& target, | 376 ImageAccessor* DicomImageDecoder::DecodeUncompressedImage(DcmDataset& dataset, |
378 DcmDataset& dataset, | 377 unsigned int frame) |
379 unsigned int frame) | |
380 { | 378 { |
381 if (!IsUncompressedImage(dataset)) | 379 if (!IsUncompressedImage(dataset)) |
382 { | 380 { |
383 throw OrthancException(ErrorCode_BadParameterType); | 381 throw OrthancException(ErrorCode_BadParameterType); |
384 } | 382 } |
385 | 383 |
386 DecodeUncompressedImageInternal(target, dataset, frame); | 384 return DecodeUncompressedImageInternal(dataset, frame); |
387 } | 385 } |
388 | 386 |
389 | 387 |
390 void DicomImageDecoder::DecodeUncompressedImageInternal(ImageBuffer& target, | 388 ImageAccessor* DicomImageDecoder::DecodeUncompressedImageInternal(DcmDataset& dataset, |
391 DcmDataset& dataset, | 389 unsigned int frame) |
392 unsigned int frame) | |
393 { | 390 { |
394 ImageSource source; | 391 ImageSource source; |
395 source.Setup(dataset, frame); | 392 source.Setup(dataset, frame); |
396 | 393 |
397 | 394 |
398 /** | 395 /** |
399 * Resize the target image. | 396 * Resize the target image. |
400 **/ | 397 **/ |
401 | 398 |
402 SetupImageBuffer(target, dataset); | 399 std::auto_ptr<ImageAccessor> target(CreateImage(dataset)); |
403 | 400 |
404 if (source.GetWidth() != target.GetWidth() || | 401 if (source.GetWidth() != target->GetWidth() || |
405 source.GetHeight() != target.GetHeight()) | 402 source.GetHeight() != target->GetHeight()) |
406 { | 403 { |
407 throw OrthancException(ErrorCode_InternalError); | 404 throw OrthancException(ErrorCode_InternalError); |
408 } | 405 } |
409 | 406 |
410 | 407 |
411 /** | 408 /** |
412 * If the format of the DICOM buffer is natively supported, use a | 409 * If the format of the DICOM buffer is natively supported, use a |
413 * direct access to copy its values. | 410 * direct access to copy its values. |
414 **/ | 411 **/ |
415 | 412 |
416 ImageAccessor targetAccessor(target.GetAccessor()); | |
417 const DicomImageInformation& info = source.GetAccessor().GetInformation(); | 413 const DicomImageInformation& info = source.GetAccessor().GetInformation(); |
418 | 414 |
419 bool fastVersionSuccess = false; | 415 bool fastVersionSuccess = false; |
420 PixelFormat sourceFormat; | 416 PixelFormat sourceFormat; |
421 if (!info.IsPlanar() && | 417 if (!info.IsPlanar() && |
433 info.GetWidth(), | 429 info.GetWidth(), |
434 info.GetHeight(), | 430 info.GetHeight(), |
435 info.GetWidth() * GetBytesPerPixel(sourceFormat), | 431 info.GetWidth() * GetBytesPerPixel(sourceFormat), |
436 buffer + frame * frameSize); | 432 buffer + frame * frameSize); |
437 | 433 |
438 ImageProcessing::Convert(targetAccessor, sourceImage); | 434 ImageProcessing::Convert(*target, sourceImage); |
439 ImageProcessing::ShiftRight(targetAccessor, info.GetShift()); | 435 ImageProcessing::ShiftRight(*target, info.GetShift()); |
440 fastVersionSuccess = true; | 436 fastVersionSuccess = true; |
441 } | 437 } |
442 } | 438 } |
443 catch (OrthancException&) | 439 catch (OrthancException&) |
444 { | 440 { |
451 * into the target image. | 447 * into the target image. |
452 **/ | 448 **/ |
453 | 449 |
454 if (!fastVersionSuccess) | 450 if (!fastVersionSuccess) |
455 { | 451 { |
456 switch (target.GetFormat()) | 452 switch (target->GetFormat()) |
457 { | 453 { |
458 case PixelFormat_RGB24: | 454 case PixelFormat_RGB24: |
459 case PixelFormat_RGBA32: | 455 case PixelFormat_RGBA32: |
460 case PixelFormat_Grayscale8: | 456 case PixelFormat_Grayscale8: |
461 CopyPixels<uint8_t>(targetAccessor, source.GetAccessor()); | 457 CopyPixels<uint8_t>(*target, source.GetAccessor()); |
462 break; | 458 break; |
463 | 459 |
464 case PixelFormat_Grayscale16: | 460 case PixelFormat_Grayscale16: |
465 CopyPixels<uint16_t>(targetAccessor, source.GetAccessor()); | 461 CopyPixels<uint16_t>(*target, source.GetAccessor()); |
466 break; | 462 break; |
467 | 463 |
468 case PixelFormat_SignedGrayscale16: | 464 case PixelFormat_SignedGrayscale16: |
469 CopyPixels<int16_t>(targetAccessor, source.GetAccessor()); | 465 CopyPixels<int16_t>(*target, source.GetAccessor()); |
470 break; | 466 break; |
471 | 467 |
472 default: | 468 default: |
473 throw OrthancException(ErrorCode_InternalError); | 469 throw OrthancException(ErrorCode_InternalError); |
474 } | 470 } |
475 } | 471 } |
472 | |
473 return target.release(); | |
476 } | 474 } |
477 | 475 |
478 | 476 |
479 #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 | 477 #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 |
480 void DicomImageDecoder::DecodeJpegLossless(ImageBuffer& target, | 478 ImageAccessor* DicomImageDecoder::DecodeJpegLossless(DcmDataset& dataset, |
481 DcmDataset& dataset, | 479 unsigned int frame) |
482 unsigned int frame) | |
483 { | 480 { |
484 if (!IsJpegLossless(dataset)) | 481 if (!IsJpegLossless(dataset)) |
485 { | 482 { |
486 throw OrthancException(ErrorCode_BadParameterType); | 483 throw OrthancException(ErrorCode_BadParameterType); |
487 } | 484 } |
498 (dataset.getOriginalXfer(), NULL, pixelSequence).good()) | 495 (dataset.getOriginalXfer(), NULL, pixelSequence).good()) |
499 { | 496 { |
500 throw OrthancException(ErrorCode_BadFileFormat); | 497 throw OrthancException(ErrorCode_BadFileFormat); |
501 } | 498 } |
502 | 499 |
503 SetupImageBuffer(target, dataset); | 500 std::auto_ptr<ImageAccessor> target(CreateImage(dataset)); |
504 | |
505 ImageAccessor targetAccessor(target.GetAccessor()); | |
506 | 501 |
507 /** | 502 /** |
508 * The "DJLSLosslessDecoder" and "DJLSNearLosslessDecoder" in DCMTK | 503 * The "DJLSLosslessDecoder" and "DJLSNearLosslessDecoder" in DCMTK |
509 * are exactly the same, except for the "supportedTransferSyntax()" | 504 * are exactly the same, except for the "supportedTransferSyntax()" |
510 * virtual function. | 505 * virtual function. |
516 | 511 |
517 Uint32 startFragment = 0; // Default | 512 Uint32 startFragment = 0; // Default |
518 OFString decompressedColorModel; // Out | 513 OFString decompressedColorModel; // Out |
519 DJ_RPLossless representationParameter; | 514 DJ_RPLossless representationParameter; |
520 OFCondition c = decoder.decodeFrame(&representationParameter, pixelSequence, ¶meters, | 515 OFCondition c = decoder.decodeFrame(&representationParameter, pixelSequence, ¶meters, |
521 &dataset, frame, startFragment, targetAccessor.GetBuffer(), | 516 &dataset, frame, startFragment, target->GetBuffer(), |
522 targetAccessor.GetSize(), decompressedColorModel); | 517 target->GetSize(), decompressedColorModel); |
523 | 518 |
524 if (!c.good()) | 519 if (!c.good()) |
525 { | 520 { |
526 throw OrthancException(ErrorCode_InternalError); | 521 throw OrthancException(ErrorCode_InternalError); |
527 } | 522 } |
523 | |
524 return target.release(); | |
528 } | 525 } |
529 #endif | 526 #endif |
530 | 527 |
531 | 528 |
532 | 529 |
533 | 530 |
534 bool DicomImageDecoder::Decode(ImageBuffer& target, | 531 ImageAccessor* DicomImageDecoder::Decode(ParsedDicomFile& dicom, |
535 ParsedDicomFile& dicom, | 532 unsigned int frame) |
536 unsigned int frame) | |
537 { | 533 { |
538 DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); | 534 DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); |
539 | 535 |
540 if (IsUncompressedImage(dataset)) | 536 if (IsUncompressedImage(dataset)) |
541 { | 537 { |
542 DecodeUncompressedImage(target, dataset, frame); | 538 return DecodeUncompressedImage(dataset, frame); |
543 return true; | 539 } |
544 } | |
545 | |
546 | 540 |
547 #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 | 541 #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 |
548 if (IsJpegLossless(dataset)) | 542 if (IsJpegLossless(dataset)) |
549 { | 543 { |
550 LOG(INFO) << "Decoding a JPEG-LS image"; | 544 LOG(INFO) << "Decoding a JPEG-LS image"; |
551 DecodeJpegLossless(target, dataset, frame); | 545 return DecodeJpegLossless(dataset, frame); |
552 return true; | |
553 } | 546 } |
554 #endif | 547 #endif |
555 | 548 |
556 | 549 |
557 #if ORTHANC_JPEG_ENABLED == 1 | 550 #if ORTHANC_JPEG_ENABLED == 1 |
558 // TODO Implement this part to speed up JPEG decompression | 551 // TODO Implement this part to speed up JPEG decompression |
559 #endif | 552 #endif |
560 | |
561 | 553 |
562 /** | 554 /** |
563 * This DICOM image format is not natively supported by | 555 * This DICOM image format is not natively supported by |
564 * Orthanc. As a last resort, try and decode it through | 556 * Orthanc. As a last resort, try and decode it through |
565 * DCMTK. This will result in higher memory consumption. This is | 557 * DCMTK. This will result in higher memory consumption. This is |
573 std::auto_ptr<DcmDataset> converted(dynamic_cast<DcmDataset*>(dataset.clone())); | 565 std::auto_ptr<DcmDataset> converted(dynamic_cast<DcmDataset*>(dataset.clone())); |
574 converted->chooseRepresentation(EXS_LittleEndianExplicit, NULL); | 566 converted->chooseRepresentation(EXS_LittleEndianExplicit, NULL); |
575 | 567 |
576 if (converted->canWriteXfer(EXS_LittleEndianExplicit)) | 568 if (converted->canWriteXfer(EXS_LittleEndianExplicit)) |
577 { | 569 { |
578 DecodeUncompressedImageInternal(target, *converted, frame); | 570 return DecodeUncompressedImageInternal(*converted, frame); |
579 return true; | 571 } |
580 } | 572 } |
581 } | 573 |
582 | 574 return NULL; |
583 return false; | |
584 } | 575 } |
585 | 576 |
586 | 577 |
587 static bool IsColorImage(PixelFormat format) | 578 static bool IsColorImage(PixelFormat format) |
588 { | 579 { |
589 return (format == PixelFormat_RGB24 || | 580 return (format == PixelFormat_RGB24 || |
590 format == PixelFormat_RGBA32); | 581 format == PixelFormat_RGBA32); |
591 } | 582 } |
592 | 583 |
593 | 584 |
594 bool DicomImageDecoder::TruncateDecodedImage(ImageBuffer& target, | 585 bool DicomImageDecoder::TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image, |
595 ImageBuffer& source, | |
596 PixelFormat format, | 586 PixelFormat format, |
597 bool allowColorConversion) | 587 bool allowColorConversion) |
598 { | 588 { |
599 // If specified, prevent the conversion between color and | 589 // If specified, prevent the conversion between color and |
600 // grayscale images | 590 // grayscale images |
601 bool isSourceColor = IsColorImage(source.GetFormat()); | 591 bool isSourceColor = IsColorImage(image->GetFormat()); |
602 bool isTargetColor = IsColorImage(format); | 592 bool isTargetColor = IsColorImage(format); |
603 | 593 |
604 if (!allowColorConversion) | 594 if (!allowColorConversion) |
605 { | 595 { |
606 if (isSourceColor ^ isTargetColor) | 596 if (isSourceColor ^ isTargetColor) |
607 { | 597 { |
608 return false; | 598 return false; |
609 } | 599 } |
610 } | 600 } |
611 | 601 |
612 if (source.GetFormat() == format) | 602 if (image->GetFormat() != format) |
613 { | 603 { |
614 // No conversion is required, return the temporary image | 604 // A conversion is required |
615 target.AcquireOwnership(source); | 605 std::auto_ptr<ImageAccessor> target(new Image(format, image->GetWidth(), image->GetHeight())); |
616 return true; | 606 ImageProcessing::Convert(*target, *image); |
617 } | 607 image = target; |
618 | 608 } |
619 target.SetFormat(format); | |
620 target.SetWidth(source.GetWidth()); | |
621 target.SetHeight(source.GetHeight()); | |
622 | |
623 ImageAccessor targetAccessor(target.GetAccessor()); | |
624 ImageAccessor sourceAccessor(source.GetAccessor()); | |
625 ImageProcessing::Convert(targetAccessor, sourceAccessor); | |
626 | 609 |
627 return true; | 610 return true; |
628 } | 611 } |
629 | 612 |
630 | 613 |
631 bool DicomImageDecoder::PreviewDecodedImage(ImageBuffer& target, | 614 bool DicomImageDecoder::PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image) |
632 ImageBuffer& source) | 615 { |
633 { | 616 switch (image->GetFormat()) |
634 switch (source.GetFormat()) | |
635 { | 617 { |
636 case PixelFormat_RGB24: | 618 case PixelFormat_RGB24: |
637 { | 619 { |
638 // Directly return color images (RGB) | 620 // Directly return color images without modification (RGB) |
639 target.AcquireOwnership(source); | |
640 return true; | 621 return true; |
641 } | 622 } |
642 | 623 |
643 case PixelFormat_Grayscale8: | 624 case PixelFormat_Grayscale8: |
644 case PixelFormat_Grayscale16: | 625 case PixelFormat_Grayscale16: |
645 case PixelFormat_SignedGrayscale16: | 626 case PixelFormat_SignedGrayscale16: |
646 { | 627 { |
647 // Grayscale image: Stretch its dynamics to the [0,255] range | 628 // Grayscale image: Stretch its dynamics to the [0,255] range |
648 target.SetFormat(PixelFormat_Grayscale8); | |
649 target.SetWidth(source.GetWidth()); | |
650 target.SetHeight(source.GetHeight()); | |
651 | |
652 ImageAccessor targetAccessor(target.GetAccessor()); | |
653 ImageAccessor sourceAccessor(source.GetAccessor()); | |
654 | |
655 int64_t a, b; | 629 int64_t a, b; |
656 ImageProcessing::GetMinMaxValue(a, b, sourceAccessor); | 630 ImageProcessing::GetMinMaxValue(a, b, *image); |
657 | 631 |
658 if (a == b) | 632 if (a == b) |
659 { | 633 { |
660 ImageProcessing::Set(targetAccessor, 0); | 634 ImageProcessing::Set(*image, 0); |
661 } | 635 } |
662 else | 636 else |
663 { | 637 { |
664 ImageProcessing::ShiftScale(sourceAccessor, static_cast<float>(-a), 255.0f / static_cast<float>(b - a)); | 638 ImageProcessing::ShiftScale(*image, static_cast<float>(-a), 255.0f / static_cast<float>(b - a)); |
665 | 639 } |
666 if (source.GetFormat() == PixelFormat_Grayscale8) | 640 |
667 { | 641 // If the source image is not grayscale 8bpp, convert it |
668 target.AcquireOwnership(source); | 642 if (image->GetFormat() != PixelFormat_Grayscale8) |
669 } | 643 { |
670 else | 644 std::auto_ptr<ImageAccessor> target(new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight())); |
671 { | 645 ImageProcessing::Convert(*target, *image); |
672 ImageProcessing::Convert(targetAccessor, sourceAccessor); | 646 image = target; |
673 } | |
674 } | 647 } |
675 | 648 |
676 return true; | 649 return true; |
677 } | 650 } |
678 | 651 |