[med-svn] [Git][med-team/orthanc-wsi][upstream] New upstream version 3.2+dfsg
Sebastien Jodogne (@jodogne-guest)
gitlab at salsa.debian.org
Tue Apr 8 16:38:26 BST 2025
Sebastien Jodogne pushed to branch upstream at Debian Med / orthanc-wsi
Commits:
7d17a3ba by jodogne-guest at 2025-04-08T17:21:28+02:00
New upstream version 3.2+dfsg
- - - - -
29 changed files:
- .hg_archival.txt
- Applications/ApplicationToolbox.cpp
- Applications/CMakeLists.txt
- Applications/DicomToTiff.cpp
- Applications/Dicomizer.cpp
- CITATION.cff
- Framework/Enumerations.cpp
- Framework/Enumerations.h
- Framework/ImageToolbox.cpp
- Framework/ImageToolbox.h
- Framework/Inputs/DicomPyramid.cpp
- Framework/Inputs/DicomPyramidInstance.cpp
- Framework/Inputs/DicomPyramidInstance.h
- Framework/Inputs/DicomPyramidLevel.cpp
- Framework/Outputs/DicomPyramidWriter.cpp
- Framework/Outputs/DicomPyramidWriter.h
- Framework/Outputs/HierarchicalTiffWriter.cpp
- Framework/Outputs/HierarchicalTiffWriter.h
- Framework/Outputs/MultiframeDicomWriter.cpp
- Framework/Outputs/PyramidWriterBase.cpp
- Framework/Outputs/PyramidWriterBase.h
- NEWS
- Resources/CMake/Version.cmake
- Resources/Orthanc/CMake/Compiler.cmake
- Resources/Orthanc/CMake/DownloadOrthancFramework.cmake
- ViewerPlugin/CMakeLists.txt
- ViewerPlugin/OrthancPyramidFrameFetcher.cpp
- ViewerPlugin/OrthancPyramidFrameFetcher.h
- ViewerPlugin/RawTile.cpp
Changes:
=====================================
.hg_archival.txt
=====================================
@@ -1,6 +1,6 @@
repo: 4a7a53257c7df5a97aea39377b8c9a6e815c9763
-node: 61a748cd203c81fc7133725f96a1cc05a3f97610
-branch: OrthancWSI-3.1
+node: 6d9987f3061295004737628903bfcbca15968f4f
+branch: OrthancWSI-3.2
latesttag: null
-latesttagdistance: 356
-changessincelatesttag: 365
+latesttagdistance: 372
+changessincelatesttag: 382
=====================================
Applications/ApplicationToolbox.cpp
=====================================
@@ -71,11 +71,19 @@ namespace OrthancWSI
Orthanc::HttpClient::GlobalInitialize();
Orthanc::FromDcmtkBridge::InitializeDictionary(false /* don't load private dictionary */);
assert(DisplayPerformanceWarning());
+
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+ Orthanc::FromDcmtkBridge::InitializeCodecs();
+#endif
}
void GlobalFinalize()
{
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+ Orthanc::FromDcmtkBridge::FinalizeCodecs();
+#endif
+
OrthancWSI::OpenSlideLibrary::Finalize();
Orthanc::HttpClient::GlobalFinalize();
Orthanc::Toolbox::FinalizeOpenSsl();
=====================================
Applications/CMakeLists.txt
=====================================
@@ -19,7 +19,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 2.8...4.0)
project(OrthancWSIApplications)
include(${CMAKE_SOURCE_DIR}/../Resources/CMake/Version.cmake)
@@ -51,7 +51,13 @@ include(${CMAKE_SOURCE_DIR}/../Resources/Orthanc/CMake/DownloadOrthancFramework.
if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
if (ORTHANC_FRAMEWORK_USE_SHARED)
- include(FindBoost)
+ # https://cmake.org/cmake/help/latest/policy/CMP0167.html
+ if (CMAKE_VERSION VERSION_GREATER "3.30")
+ find_package(Boost CONFIG)
+ else()
+ include(FindBoost)
+ endif()
+
find_package(Boost COMPONENTS filesystem program_options regex system thread date_time)
if (NOT Boost_FOUND)
@@ -75,7 +81,8 @@ else()
SET(ENABLE_CRYPTO_OPTIONS ON)
SET(ENABLE_DCMTK ON)
SET(ENABLE_DCMTK_JPEG OFF) # Disable DCMTK's support for JPEG, that clashes with libtiff
- SET(ENABLE_DCMTK_JPEG_LOSSLESS OFF) # Disable DCMTK's support for JPEG-LS
+ SET(ENABLE_DCMTK_JPEG_LOSSLESS ON) # Enable DCMTK's support for JPEG-LS (was disabled in WSI <= 3.1)
+ SET(ENABLE_DCMTK_TRANSCODING ON) # Enable DCMTK's support for transcoding (was disabled in WSI <= 3.1)
SET(ENABLE_DCMTK_NETWORKING OFF) # Disable DCMTK's support for DICOM networking
SET(ENABLE_JPEG ON)
SET(ENABLE_LOCALE ON) # Enable support for locales (notably in Boost)
@@ -89,9 +96,19 @@ else()
include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
include(${ORTHANC_WSI_DIR}/Resources/CMake/BoostExtendedConfiguration.cmake)
+
+ if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK)
+ # This is a backport of changeset: https://orthanc.uclouvain.be/hg/orthanc/rev/97d69fb5958e
+ # TODO - Remove after next upgrade to Orthanc framework > 1.12.7
+ include_directories(
+ ${DCMTK_SOURCES_DIR}/dcmimgle/include
+ )
+ endif()
endif()
+
+
# Include components specific to WSI
include(${ORTHANC_WSI_DIR}/Resources/CMake/OpenJpegConfiguration.cmake)
include(${ORTHANC_WSI_DIR}/Resources/CMake/LibTiffConfiguration.cmake)
=====================================
Applications/DicomToTiff.cpp
=====================================
@@ -333,7 +333,7 @@ int main(int argc, char* argv[])
}
catch (Orthanc::OrthancException& e)
{
- LOG(ERROR) << "Terminating on exception: " << e.What();
+ LOG(ERROR) << "Terminating on exception: " << e.What() << ": " << e.GetDetails();;
if (options.count(OPTION_REENCODE) == 0)
{
=====================================
Applications/Dicomizer.cpp
=====================================
@@ -248,14 +248,22 @@ static void Recompress(OrthancWSI::IFileTarget& output,
switch (parameters.GetTargetCompression())
{
case OrthancWSI::ImageCompression_Jpeg:
- case OrthancWSI::ImageCompression_Jpeg2000:
targetPhotometric = Orthanc::PhotometricInterpretation_YBRFull422;
break;
+ case OrthancWSI::ImageCompression_Jpeg2000:
+ // Was set to Orthanc::PhotometricInterpretation_YBRFull422 in WSI <= 3.1
+ targetPhotometric = Orthanc::PhotometricInterpretation_RGB;
+ break;
+
case OrthancWSI::ImageCompression_None:
targetPhotometric = Orthanc::PhotometricInterpretation_RGB;
break;
+ case OrthancWSI::ImageCompression_JpegLS:
+ targetPhotometric = Orthanc::PhotometricInterpretation_RGB;
+ break;
+
default:
throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
}
@@ -670,7 +678,7 @@ static bool ParseParameters(int& exitStatus,
(OPTION_TILE_HEIGHT, boost::program_options::value<int>(),
"Height of the tiles in the target image")
(OPTION_COMPRESSION, boost::program_options::value<std::string>(),
- "Compression of the target image (\"none\", \"jpeg\" or \"jpeg2000\")")
+ "Compression of the target image (\"none\", \"jpeg\", \"jpeg2000\", or \"jpeg-ls\")")
(OPTION_JPEG_QUALITY, boost::program_options::value<int>(),
"Set quality level for JPEG (0..100)")
(OPTION_MAX_SIZE, boost::program_options::value<int>()->default_value(10),
@@ -956,6 +964,10 @@ static bool ParseParameters(int& exitStatus,
{
parameters.SetTargetCompression(OrthancWSI::ImageCompression_Jpeg2000);
}
+ else if (s == "jpeg-ls")
+ {
+ parameters.SetTargetCompression(OrthancWSI::ImageCompression_JpegLS);
+ }
else
{
LOG(ERROR) << "Unknown image compression for the target image: " << s;
@@ -1293,7 +1305,7 @@ int main(int argc, char* argv[])
}
catch (Orthanc::OrthancException& e)
{
- LOG(ERROR) << "Terminating on exception: " << e.What();
+ LOG(ERROR) << "Terminating on exception: " << e.What() << ": " << e.GetDetails();
exitStatus = -1;
}
=====================================
CITATION.cff
=====================================
@@ -37,5 +37,5 @@ authors:
doi: "10.5220/0006155100810087"
license: "AGPL-3.0-or-later"
repository-code: "https://orthanc.uclouvain.be/hg/orthanc-wsi/"
-version: "3.1"
-date-released: 2025-03-17
+version: "3.2"
+date-released: 2025-04-08
=====================================
Framework/Enumerations.cpp
=====================================
@@ -63,6 +63,9 @@ namespace OrthancWSI
case ImageCompression_Dicom:
return "DICOM";
+ case ImageCompression_JpegLS:
+ return "JPEG-LS";
+
default:
throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
}
=====================================
Framework/Enumerations.h
=====================================
@@ -42,7 +42,9 @@ namespace OrthancWSI
ImageCompression_Png = 4,
ImageCompression_Jpeg = 5,
ImageCompression_Jpeg2000 = 6,
- ImageCompression_Tiff = 7
+ ImageCompression_Tiff = 7,
+ ImageCompression_UseOrthancPreview = 8,
+ ImageCompression_JpegLS = 9
};
enum OpticalPath
=====================================
Framework/ImageToolbox.cpp
=====================================
@@ -169,6 +169,20 @@ namespace OrthancWSI
}
+ void EncodeUncompressedTile(std::string& target,
+ const Orthanc::ImageAccessor& source)
+ {
+ unsigned int pitch = GetBytesPerPixel(source.GetFormat()) * source.GetWidth();
+ target.resize(pitch * source.GetHeight());
+
+ const unsigned int height = source.GetHeight();
+ for (unsigned int i = 0; i < height; i++)
+ {
+ memcpy(&target[i * pitch], source.GetConstRow(i), pitch);
+ }
+ }
+
+
void EncodeTile(std::string& target,
const Orthanc::ImageAccessor& source,
ImageCompression compression,
@@ -176,14 +190,7 @@ namespace OrthancWSI
{
if (compression == ImageCompression_None)
{
- unsigned int pitch = GetBytesPerPixel(source.GetFormat()) * source.GetWidth();
- target.resize(pitch * source.GetHeight());
-
- const unsigned int height = source.GetHeight();
- for (unsigned int i = 0; i < height; i++)
- {
- memcpy(&target[i * pitch], source.GetConstRow(i), pitch);
- }
+ EncodeUncompressedTile(target, source);
}
else
{
@@ -356,5 +363,54 @@ namespace OrthancWSI
throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
}
}
+
+
+ bool HasPngSignature(const std::string& buffer)
+ {
+ if (buffer.size() < 8)
+ {
+ return false;
+ }
+ else
+ {
+ // https://en.wikipedia.org/wiki/PNG#File_header
+ // https://en.wikipedia.org/wiki/List_of_file_signatures
+ const uint8_t* p = reinterpret_cast<const uint8_t*>(buffer.data());
+ return (p[0] == 0x89 &&
+ p[1] == 0x50 &&
+ p[2] == 0x4e &&
+ p[3] == 0x47 &&
+ p[4] == 0x0d &&
+ p[5] == 0x0a &&
+ p[6] == 0x1a &&
+ p[7] == 0x0a);
+ }
+ }
+
+
+ bool HasJpegSignature(const std::string& buffer)
+ {
+ if (buffer.size() < 18)
+ {
+ return false;
+ }
+ else
+ {
+ // https://en.wikipedia.org/wiki/List_of_file_signatures
+ const uint8_t* p = reinterpret_cast<const uint8_t*>(buffer.data());
+ if (p[0] != 0xff ||
+ p[1] != 0xd8 ||
+ p[2] != 0xff)
+ {
+ return false;
+ }
+
+ // This is only a rough guess!
+ return (p[3] == 0xdb ||
+ p[3] == 0xe0 ||
+ p[3] == 0xee ||
+ p[3] == 0xe1);
+ }
+ }
}
}
=====================================
Framework/ImageToolbox.h
=====================================
@@ -58,6 +58,9 @@ namespace OrthancWSI
unsigned int width,
unsigned int height);
+ void EncodeUncompressedTile(std::string& target,
+ const Orthanc::ImageAccessor& source);
+
void EncodeTile(std::string& target,
const Orthanc::ImageAccessor& source,
ImageCompression compression,
@@ -77,5 +80,9 @@ namespace OrthancWSI
void ConvertJpegYCbCrToRgb(Orthanc::ImageAccessor& image /* inplace */);
ImageCompression Convert(Orthanc::MimeType type);
+
+ bool HasPngSignature(const std::string& buffer);
+
+ bool HasJpegSignature(const std::string& buffer);
}
}
=====================================
Framework/Inputs/DicomPyramid.cpp
=====================================
@@ -192,6 +192,8 @@ namespace OrthancWSI
for (size_t i = 0; i < instances_.size(); i++)
{
+ assert(instances_[i] != NULL);
+
if (i == 0 ||
instances_[i - 1]->GetTotalWidth() != instances_[i]->GetTotalWidth())
{
@@ -202,6 +204,8 @@ namespace OrthancWSI
assert(levels_.back() != NULL);
levels_.back()->AddInstance(*instances_[i]);
}
+
+ instances_[i]->SetLevel(levels_.size() - 1);
}
}
@@ -283,7 +287,8 @@ namespace OrthancWSI
{
assert(instances_[i] != NULL);
- if (instances_[i]->HasImagedVolumeSize())
+ if (instances_[i]->IsLevel(0) && // Only consider the finest level
+ instances_[i]->HasImagedVolumeSize())
{
if (!found)
{
=====================================
Framework/Inputs/DicomPyramidInstance.cpp
=====================================
@@ -82,6 +82,17 @@ namespace OrthancWSI
{
return ImageCompression_Jpeg2000;
}
+ else if (s == "1.2.840.10008.1.2.1.99" ||
+ s == "1.2.840.10008.1.2.2" ||
+ s == "1.2.840.10008.1.2.4.51" ||
+ s == "1.2.840.10008.1.2.4.57" ||
+ s == "1.2.840.10008.1.2.4.70" ||
+ s == "1.2.840.10008.1.2.4.80" ||
+ s == "1.2.840.10008.1.2.4.81" ||
+ s == "1.2.840.10008.1.2.5")
+ {
+ return ImageCompression_UseOrthancPreview;
+ }
else
{
LOG(ERROR) << "Unsupported transfer syntax: " << s;
@@ -305,7 +316,9 @@ namespace OrthancWSI
backgroundBlue_(0),
hasImagedVolumeSize_(false),
imagedVolumeWidth_(0),
- imagedVolumeHeight_(0)
+ imagedVolumeHeight_(0),
+ hasLevel_(false),
+ level_(0)
{
if (useCache)
{
@@ -570,4 +583,25 @@ namespace OrthancWSI
throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
}
}
+
+
+ void DicomPyramidInstance::SetLevel(unsigned int level)
+ {
+ if (hasLevel_)
+ {
+ throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+ }
+ else
+ {
+ level_ = level;
+ hasLevel_ = true;
+ }
+ }
+
+
+ bool DicomPyramidInstance::IsLevel(unsigned int level) const
+ {
+ return (hasLevel_ &&
+ level_ == level);
+ }
}
=====================================
Framework/Inputs/DicomPyramidInstance.h
=====================================
@@ -54,6 +54,8 @@ namespace OrthancWSI
bool hasImagedVolumeSize_;
double imagedVolumeWidth_;
double imagedVolumeHeight_;
+ bool hasLevel_;
+ unsigned int level_;
void Load(OrthancStone::IOrthancConnection& orthanc,
const std::string& instanceId);
@@ -137,5 +139,9 @@ namespace OrthancWSI
double GetImagedVolumeWidth() const;
double GetImagedVolumeHeight() const;
+
+ void SetLevel(unsigned int level);
+
+ bool IsLevel(unsigned int level) const;
};
}
=====================================
Framework/Inputs/DicomPyramidLevel.cpp
=====================================
@@ -24,6 +24,8 @@
#include "../PrecompiledHeadersWSI.h"
#include "DicomPyramidLevel.h"
+#include "../ImageToolbox.h"
+
#include <Logging.h>
#include <OrthancException.h>
@@ -132,15 +134,39 @@ namespace OrthancWSI
assert(tile.instance_ != NULL);
DicomPyramidInstance& instance = *tile.instance_;
- std::string uri = ("/instances/" + instance.GetInstanceId() +
- "/frames/" + boost::lexical_cast<std::string>(tile.frame_) + "/raw");
-
- orthanc.RestApiGet(raw, uri);
-
compression = instance.GetImageCompression(orthanc);
format = instance.GetPixelFormat();
- return true;
+ if (compression == ImageCompression_UseOrthancPreview)
+ {
+ // If the WSI viewer plugin has no built-in support for this transfer syntax,
+ // use the decoding primitives offered by the Orthanc core
+ std::string uri = ("/instances/" + instance.GetInstanceId() +
+ "/frames/" + boost::lexical_cast<std::string>(tile.frame_) + "/preview");
+ orthanc.RestApiGet(raw, uri);
+
+ if (ImageToolbox::HasPngSignature(raw)) // In theory, Orthanc should always generate PNG by default
+ {
+ compression = ImageCompression_Png;
+ return true;
+ }
+ else if (ImageToolbox::HasJpegSignature(raw))
+ {
+ compression = ImageCompression_Jpeg;
+ return true;
+ }
+ else
+ {
+ throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Cannot decode a preview image generated by the Orthanc core");
+ }
+ }
+ else
+ {
+ std::string uri = ("/instances/" + instance.GetInstanceId() +
+ "/frames/" + boost::lexical_cast<std::string>(tile.frame_) + "/raw");
+ orthanc.RestApiGet(raw, uri);
+ return true;
+ }
}
else
{
=====================================
Framework/Outputs/DicomPyramidWriter.cpp
=====================================
@@ -25,6 +25,7 @@
#include "DicomPyramidWriter.h"
#include "../DicomToolbox.h"
+#include "../ImageToolbox.h"
#include <Compatibility.h> // For std::unique_ptr
#include <Logging.h>
@@ -198,6 +199,21 @@ namespace OrthancWSI
}
+ void DicomPyramidWriter::EncodeTileInternal(std::string& encoded,
+ const Orthanc::ImageAccessor& tile)
+ {
+ if (GetImageCompression() == ImageCompression_JpegLS)
+ {
+ // For JPEG-LS, images are first store uncompressed, then transcoded in "MultiframeDicomWriter"
+ ImageToolbox::EncodeUncompressedTile(encoded, tile);
+ }
+ else
+ {
+ ImageToolbox::EncodeTile(encoded, tile, GetImageCompression(), GetJpegQuality());
+ }
+ }
+
+
DicomPyramidWriter::DicomPyramidWriter(IFileTarget& target,
const DcmDataset& dataset,
Orthanc::PixelFormat pixelFormat,
=====================================
Framework/Outputs/DicomPyramidWriter.h
=====================================
@@ -65,6 +65,9 @@ namespace OrthancWSI
{
}
+ virtual void EncodeTileInternal(std::string& encoded,
+ const Orthanc::ImageAccessor& tile) ORTHANC_OVERRIDE;
+
public:
DicomPyramidWriter(IFileTarget& target,
const DcmDataset& dataset,
=====================================
Framework/Outputs/HierarchicalTiffWriter.cpp
=====================================
@@ -24,6 +24,8 @@
#include "../PrecompiledHeadersWSI.h"
#include "HierarchicalTiffWriter.h"
+#include "../ImageToolbox.h"
+
#include <Logging.h>
#include <OrthancException.h>
#include <TemporaryFile.h>
@@ -415,6 +417,13 @@ namespace OrthancWSI
}
+ void HierarchicalTiffWriter::EncodeTileInternal(std::string& encoded,
+ const Orthanc::ImageAccessor& tile)
+ {
+ ImageToolbox::EncodeTile(encoded, tile, GetImageCompression(), GetJpegQuality());
+ }
+
+
HierarchicalTiffWriter::HierarchicalTiffWriter(const std::string& path,
Orthanc::PixelFormat pixelFormat,
ImageCompression compression,
=====================================
Framework/Outputs/HierarchicalTiffWriter.h
=====================================
@@ -75,6 +75,8 @@ namespace OrthancWSI
virtual void AddLevelInternal(const Level& level) ORTHANC_OVERRIDE;
+ virtual void EncodeTileInternal(std::string& encoded,
+ const Orthanc::ImageAccessor& tile) ORTHANC_OVERRIDE;
public:
HierarchicalTiffWriter(const std::string& path,
=====================================
Framework/Outputs/MultiframeDicomWriter.cpp
=====================================
@@ -31,6 +31,10 @@
#include <Logging.h>
#include <OrthancException.h>
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+# include <DicomParsing/DcmtkTranscoder.h>
+#endif
+
#include <dcmtk/dcmdata/dcuid.h>
#include <dcmtk/dcmdata/dcdeftag.h>
#include <dcmtk/dcmdata/dcostrmb.h>
@@ -182,6 +186,10 @@ namespace OrthancWSI
transferSyntax_ = EXS_JPEG2000LosslessOnly;
break;
+ case ImageCompression_JpegLS:
+ transferSyntax_ = EXS_JPEGLSLossless;
+ break;
+
default:
throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
}
@@ -251,7 +259,8 @@ namespace OrthancWSI
// Free the functional group on error
std::unique_ptr<DcmItem> functionalGroupRaii(functionalGroup);
- if (compression_ == ImageCompression_None)
+ if (compression_ == ImageCompression_None ||
+ compression_ == ImageCompression_JpegLS)
{
if (frame.size() != uncompressedFrameSize_)
{
@@ -313,24 +322,58 @@ namespace OrthancWSI
DicomToolbox::SetUint16Tag(*dicom->getDataset(), DCM_InConcatenationNumber, countInstances_);
}
- switch (compression_)
+ if (compression_ == ImageCompression_JpegLS)
{
- case ImageCompression_None:
- InjectUncompressedPixelData(*dicom);
- break;
+#if (ORTHANC_ENABLE_DCMTK_TRANSCODING == 1) && (ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1)
+ const Orthanc::DicomTransferSyntax syntax = Orthanc::DicomTransferSyntax_JPEGLSLossless;
- case ImageCompression_Jpeg:
- case ImageCompression_Jpeg2000:
- offsetTable_->createOffsetTable(*offsetList_);
- dicom->getDataset()->insert(compressedPixelSequence_.release());
- break;
+ InjectUncompressedPixelData(*dicom);
- default:
- throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+ Orthanc::IDicomTranscoder::DicomImage source;
+ source.AcquireParsed(dicom.release()); // "dicom" is invalid below this point
+
+ std::set<Orthanc::DicomTransferSyntax> s;
+ s.insert(syntax);
+
+ Orthanc::IDicomTranscoder::DicomImage transcoded;
+
+ Orthanc::DcmtkTranscoder transcoder(1);
+ if (transcoder.Transcode(transcoded, source, s, true))
+ {
+ ResetImage();
+ SaveDicomToMemory(target, transcoded.GetParsed(), transferSyntax_);
+ return; // Success
+ }
+ else
+ {
+ throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "DCMTK cannot transcode to " +
+ std::string(Orthanc::GetTransferSyntaxUid(syntax)));
+ }
+#else
+ throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "DCMTK was compiled without support for JPEG-LS");
+#endif
}
+ else
+ {
+ switch (compression_)
+ {
+ case ImageCompression_None:
+ InjectUncompressedPixelData(*dicom);
+ break;
+
+ case ImageCompression_Jpeg:
+ case ImageCompression_Jpeg2000:
+ offsetTable_->createOffsetTable(*offsetList_);
+ dicom->getDataset()->insert(compressedPixelSequence_.release());
+ break;
+
+ default:
+ throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+ }
- ResetImage();
+ ResetImage();
- SaveDicomToMemory(target, *dicom, transferSyntax_);
+ SaveDicomToMemory(target, *dicom, transferSyntax_);
+ }
}
}
=====================================
Framework/Outputs/PyramidWriterBase.cpp
=====================================
@@ -148,7 +148,8 @@ namespace OrthancWSI
const Level level = GetLevel(z);
std::string raw;
- ImageToolbox::EncodeTile(raw, tile, compression_, jpegQuality_);
+ EncodeTileInternal(raw, tile);
+
WriteRawTileInternal(raw, level, x, y);
}
}
=====================================
Framework/Outputs/PyramidWriterBase.h
=====================================
@@ -51,6 +51,9 @@ namespace OrthancWSI
// This function is invoked before any call to WriteRawTileInternal()
virtual void AddLevelInternal(const Level& level) = 0;
+ virtual void EncodeTileInternal(std::string& encoded,
+ const Orthanc::ImageAccessor& tile) = 0;
+
private:
boost::mutex mutex_; // This mutex protects access to the levels
Orthanc::PixelFormat pixelFormat_;
=====================================
NEWS
=====================================
@@ -2,6 +2,17 @@ Pending changes in the mainline
===============================
+Version 3.2 (2025-04-08)
+========================
+
+* Support windowing when rendering grayscale images using on-the-fly deep zoom
+* Added tolerance to imaged volume width/height by looking only at the finest level
+* Added support for more transfer syntaxes in the Web viewer plugin, including JPEG-LS
+* Added support for JPEG-LS in OrthancWSIDicomizer with argument "--compression=jpeg-ls"
+* Fix photometric interpretation of JPEG2000 images generated by OrthancWSIDicomizer
+ (now set to RGB instead of YBR_FULL_422)
+
+
Version 3.1 (2025-03-17)
========================
@@ -29,7 +40,7 @@ Version 3.0 (2024-12-20)
=> Minimum SDK version: 1.7.0 <=
-* On-the-fly creation of pyramids from frames of DICOM instances
+* On-the-fly creation of pyramids from frames of DICOM instances ("deep zoom" button)
Version 2.1 (2024-10-18)
=====================================
Resources/CMake/Version.cmake
=====================================
@@ -19,13 +19,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-set(ORTHANC_WSI_VERSION "3.1")
+set(ORTHANC_WSI_VERSION "3.2")
if (ORTHANC_WSI_VERSION STREQUAL "mainline")
set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline")
set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
else()
- set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.12.6")
+ set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.12.7")
set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
endif()
=====================================
Resources/Orthanc/CMake/Compiler.cmake
=====================================
@@ -22,6 +22,16 @@
# This file sets all the compiler-related flags
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+ # Since Orthanc 1.12.7 that allows CMake 4.0, builds for macOS
+ # require the C++ standard to be explicitly set to C++11. Do *not*
+ # do this on GNU/Linux, as third-party system libraries could have
+ # been compiled with higher versions of the C++ standard.
+ set(CMAKE_CXX_STANDARD 11)
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
+ set(CMAKE_CXX_EXTENSIONS OFF)
+endif()
+
# Save the current compiler flags to the cache every time cmake configures the project
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}" CACHE STRING "compiler flags" FORCE)
=====================================
Resources/Orthanc/CMake/DownloadOrthancFramework.cmake
=====================================
@@ -169,6 +169,8 @@ if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" OR
set(ORTHANC_FRAMEWORK_MD5 "5bb69f092981fdcfc11dec0a0f9a7db3")
elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.6")
set(ORTHANC_FRAMEWORK_MD5 "0e971f32f4f3e4951e0f3b5de49a3da6")
+ elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.7")
+ set(ORTHANC_FRAMEWORK_MD5 "f27c27d7a7a694dab1fd7f0a99d9715a")
# Below this point are development snapshots that were used to
# release some plugin, before an official release of the Orthanc
@@ -501,7 +503,15 @@ if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
include(CheckIncludeFile)
include(CheckIncludeFileCXX)
- include(FindPythonInterp)
+
+ if(CMAKE_VERSION VERSION_GREATER "3.11")
+ find_package(Python REQUIRED COMPONENTS Interpreter)
+ set(PYTHON_EXECUTABLE ${Python_EXECUTABLE})
+ else()
+ include(FindPythonInterp)
+ find_package(PythonInterp REQUIRED)
+ endif()
+
include(${CMAKE_CURRENT_LIST_DIR}/Compiler.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/DownloadPackage.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/AutoGeneratedCode.cmake)
=====================================
ViewerPlugin/CMakeLists.txt
=====================================
@@ -19,7 +19,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 2.8...4.0)
project(OrthancWSIPlugin)
include(${CMAKE_SOURCE_DIR}/../Resources/CMake/Version.cmake)
@@ -54,7 +54,13 @@ include(${CMAKE_SOURCE_DIR}/../Resources/Orthanc/CMake/DownloadOrthancFramework.
if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
if (ORTHANC_FRAMEWORK_USE_SHARED)
- include(FindBoost)
+ # https://cmake.org/cmake/help/latest/policy/CMP0167.html
+ if (CMAKE_VERSION VERSION_GREATER "3.30")
+ find_package(Boost CONFIG)
+ else()
+ include(FindBoost)
+ endif()
+
find_package(Boost COMPONENTS system thread)
if (NOT Boost_FOUND)
=====================================
ViewerPlugin/OrthancPyramidFrameFetcher.cpp
=====================================
@@ -26,6 +26,8 @@
#include "../Framework/Inputs/OnTheFlyPyramid.h"
+#include <DicomFormat/DicomImageInformation.h>
+#include <DicomFormat/DicomMap.h>
#include <Images/Image.h>
#include <Images/ImageProcessing.h>
@@ -34,32 +36,6 @@
namespace OrthancWSI
{
- void OrthancPyramidFrameFetcher::RenderGrayscale(Orthanc::ImageAccessor& target,
- const Orthanc::ImageAccessor& source)
- {
- Orthanc::Image converted(Orthanc::PixelFormat_Float32, source.GetWidth(), source.GetHeight(), false);
- Orthanc::ImageProcessing::Convert(converted, source);
-
- float minValue, maxValue;
- Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, converted);
-
- assert(minValue <= maxValue);
- if (std::abs(maxValue - minValue) < 0.0001)
- {
- Orthanc::ImageProcessing::Set(target, 0);
- }
- else
- {
- const float scaling = 255.0f / (maxValue - minValue);
- const float offset = -minValue;
-
- Orthanc::Image rescaled(Orthanc::PixelFormat_Grayscale8, source.GetWidth(), source.GetHeight(), false);
- Orthanc::ImageProcessing::ShiftScale(rescaled, converted, static_cast<float>(offset), static_cast<float>(scaling), false);
- Orthanc::ImageProcessing::Convert(target, rescaled);
- }
- }
-
-
OrthancPyramidFrameFetcher::OrthancPyramidFrameFetcher(OrthancStone::IOrthancConnection* orthanc,
bool smooth) :
orthanc_(orthanc),
@@ -123,106 +99,101 @@ namespace OrthancWSI
OrthancPlugins::DicomInstance dicom(buffer.GetData(), buffer.GetSize());
- uint8_t backgroundRed = defaultBackgroundRed_;
- uint8_t backgroundGreen = defaultBackgroundGreen_;
- uint8_t backgroundBlue = defaultBackgroundBlue_;
-
Json::Value tags;
- dicom.GetSimplifiedJson(tags);
+ dicom.GetJson(tags);
- static const char* const PHOTOMETRIC_INTERPRETATION = "PhotometricInterpretation";
+ Orthanc::DicomMap m;
+ m.FromDicomAsJson(tags);
- if (tags.isMember(PHOTOMETRIC_INTERPRETATION) &&
- tags[PHOTOMETRIC_INTERPRETATION].type() == Json::stringValue)
- {
- std::string p = tags[PHOTOMETRIC_INTERPRETATION].asString();
- if (p == "MONOCHROME1")
- {
- backgroundRed = 255;
- backgroundGreen = 255;
- backgroundBlue = 255;
- }
- else if (p == "MONOCHROME2")
- {
- backgroundRed = 0;
- backgroundGreen = 0;
- backgroundBlue = 0;
- }
- }
+ Orthanc::DicomImageInformation info(m);
- std::unique_ptr<OrthancPlugins::OrthancImage> frame(dicom.GetDecodedFrame(frameNumber));
+ uint8_t backgroundRed, backgroundGreen, backgroundBlue;
- Orthanc::PixelFormat format;
- switch (frame->GetPixelFormat())
+ if (info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome1 ||
+ info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome2)
{
- case OrthancPluginPixelFormat_RGB24:
- format = Orthanc::PixelFormat_RGB24;
- break;
-
- case OrthancPluginPixelFormat_Grayscale8:
- format = Orthanc::PixelFormat_Grayscale8;
- break;
-
- case OrthancPluginPixelFormat_Grayscale16:
- format = Orthanc::PixelFormat_Grayscale16;
- break;
-
- default:
- throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+ backgroundRed = 0;
+ backgroundGreen = 0;
+ backgroundBlue = 0;
+ }
+ else
+ {
+ backgroundRed = defaultBackgroundRed_;
+ backgroundGreen = defaultBackgroundGreen_;
+ backgroundBlue = defaultBackgroundBlue_;
}
- Orthanc::ImageAccessor source;
- source.AssignReadOnly(format, frame->GetWidth(), frame->GetHeight(), frame->GetPitch(), frame->GetBuffer());
+ std::unique_ptr<OrthancPlugins::OrthancImage> frame(dicom.GetDecodedFrame(frameNumber));
unsigned int paddedWidth, paddedHeight;
if (paddingX_ >= 2)
{
- paddedWidth = OrthancWSI::CeilingDivision(source.GetWidth(), paddingX_) * paddingX_;
+ paddedWidth = OrthancWSI::CeilingDivision(frame->GetWidth(), paddingX_) * paddingX_;
}
else
{
- paddedWidth = source.GetWidth();
+ paddedWidth = frame->GetWidth();
}
if (paddingY_ >= 2)
{
- paddedHeight = OrthancWSI::CeilingDivision(source.GetHeight(), paddingY_) * paddingY_;
+ paddedHeight = OrthancWSI::CeilingDivision(frame->GetHeight(), paddingY_) * paddingY_;
}
else
{
- paddedHeight = source.GetHeight();
+ paddedHeight = frame->GetHeight();
}
- std::unique_ptr<Orthanc::ImageAccessor> rendered(new Orthanc::Image(Orthanc::PixelFormat_RGB24, paddedWidth, paddedHeight, false));
- if (paddedWidth != source.GetWidth() ||
- paddedHeight != source.GetHeight())
+ Orthanc::PixelFormat sourceFormat, targetFormat;
+ switch (frame->GetPixelFormat())
{
- Orthanc::ImageProcessing::Set(*rendered, backgroundRed, backgroundGreen, backgroundBlue, 255 /* alpha */);
+ case OrthancPluginPixelFormat_RGB24:
+ sourceFormat = Orthanc::PixelFormat_RGB24;
+ targetFormat = Orthanc::PixelFormat_RGB24;
+ break;
+
+ case OrthancPluginPixelFormat_Grayscale8:
+ sourceFormat = Orthanc::PixelFormat_Grayscale8;
+ targetFormat = Orthanc::PixelFormat_Grayscale8;
+ break;
+
+ case OrthancPluginPixelFormat_Grayscale16:
+ sourceFormat = Orthanc::PixelFormat_Grayscale16;
+ targetFormat = Orthanc::PixelFormat_Grayscale8;
+ break;
+
+ case OrthancPluginPixelFormat_SignedGrayscale16:
+ sourceFormat = Orthanc::PixelFormat_SignedGrayscale16;
+ targetFormat = Orthanc::PixelFormat_Grayscale8;
+ break;
+
+ default:
+ throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
}
- Orthanc::ImageAccessor region;
- rendered->GetRegion(region, 0, 0, source.GetWidth(), source.GetHeight());
- switch (format)
+ std::unique_ptr<Orthanc::ImageAccessor> rendered(new Orthanc::Image(targetFormat, paddedWidth, paddedHeight, false));
+
+ if (paddedWidth != frame->GetWidth() ||
+ paddedHeight != frame->GetHeight())
{
- case Orthanc::PixelFormat_RGB24:
- Orthanc::ImageProcessing::Copy(region, source);
- break;
+ Orthanc::ImageProcessing::Set(*rendered, backgroundRed, backgroundGreen, backgroundBlue, 255 /* alpha */);
+ }
+
- case Orthanc::PixelFormat_Grayscale8:
- Orthanc::ImageProcessing::Convert(region, source);
- break;
+ {
+ Orthanc::ImageAccessor target;
+ rendered->GetRegion(target, 0, 0, frame->GetWidth(), frame->GetHeight());
- case Orthanc::PixelFormat_Grayscale16:
- RenderGrayscale(region, source);
- break;
+ Orthanc::ImageAccessor source;
+ source.AssignReadOnly(sourceFormat, frame->GetWidth(), frame->GetHeight(), frame->GetPitch(), frame->GetBuffer());
- default:
- throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+ Orthanc::ImageProcessing::RenderDefaultWindow(target, info, source);
}
+
std::unique_ptr<DecodedTiledPyramid> result(new OnTheFlyPyramid(rendered.release(), tileWidth_, tileHeight_, smooth_));
result->SetBackgroundColor(backgroundRed, backgroundGreen, backgroundBlue);
=====================================
ViewerPlugin/OrthancPyramidFrameFetcher.h
=====================================
@@ -42,9 +42,6 @@ namespace OrthancWSI
uint8_t defaultBackgroundGreen_;
uint8_t defaultBackgroundBlue_;
- static void RenderGrayscale(Orthanc::ImageAccessor& target,
- const Orthanc::ImageAccessor& source);
-
public:
OrthancPyramidFrameFetcher(OrthancStone::IOrthancConnection* orthanc,
bool smooth);
=====================================
ViewerPlugin/RawTile.cpp
=====================================
@@ -31,6 +31,7 @@
#include <Compatibility.h> // For std::unique_ptr
#include <Images/ImageProcessing.h>
#include <Images/JpegReader.h>
+#include <Images/PngReader.h>
#include <Images/PngWriter.h>
#include <MultiThreading/Semaphore.h>
#include <OrthancException.h>
@@ -56,7 +57,12 @@ namespace OrthancWSI
std::unique_ptr<Jpeg2000Reader> decoded(new Jpeg2000Reader);
decoded->ReadFromMemory(tile_);
- if (photometric_ == Orthanc::PhotometricInterpretation_YBR_ICT)
+ if (photometric_ == Orthanc::PhotometricInterpretation_YBRFull ||
+ photometric_ == Orthanc::PhotometricInterpretation_YBRFull422 ||
+ photometric_ == Orthanc::PhotometricInterpretation_YBRPartial420 ||
+ photometric_ == Orthanc::PhotometricInterpretation_YBRPartial422 ||
+ photometric_ == Orthanc::PhotometricInterpretation_YBR_ICT ||
+ photometric_ == Orthanc::PhotometricInterpretation_YBR_RCT)
{
ImageToolbox::ConvertJpegYCbCrToRgb(*decoded);
}
@@ -78,6 +84,19 @@ namespace OrthancWSI
return decoded.release();
}
+ case ImageCompression_Png:
+ {
+ /**
+ * This is used if the DICOM instance has a transfer syntax
+ * that is not natively supported by the WSI viewer plugin, in
+ * which case the tile comes from the "/preview" route of the
+ * REST API of Orthanc.
+ **/
+ std::unique_ptr<Orthanc::PngReader> decoded(new Orthanc::PngReader);
+ decoded->ReadFromMemory(tile_);
+ return decoded.release();
+ }
+
default:
throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
}
View it on GitLab: https://salsa.debian.org/med-team/orthanc-wsi/-/commit/7d17a3ba82f1e8243867734692427d31976ae7bc
--
View it on GitLab: https://salsa.debian.org/med-team/orthanc-wsi/-/commit/7d17a3ba82f1e8243867734692427d31976ae7bc
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20250408/9b0a297f/attachment-0001.htm>
More information about the debian-med-commit
mailing list