[med-svn] [Git][med-team/orthanc][upstream] New upstream version 1.9.7+dfsg
Sebastien Jodogne (@jodogne-guest)
gitlab at salsa.debian.org
Tue Aug 31 22:00:43 BST 2021
Sebastien Jodogne pushed to branch upstream at Debian Med / orthanc
Commits:
e757fddc by jodogne-guest at 2021-08-31T21:30:11+02:00
New upstream version 1.9.7+dfsg
- - - - -
26 changed files:
- .hg_archival.txt
- + CITATION.cff
- NEWS
- OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake
- OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake
- OrthancFramework/Sources/DicomFormat/DicomTag.h
- OrthancFramework/Sources/DicomFormat/DicomValue.cpp
- OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp
- OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h
- OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp
- OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h
- OrthancFramework/Sources/Images/ImageProcessing.cpp
- OrthancFramework/Sources/JobsEngine/JobInfo.cpp
- OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp
- OrthancFramework/Sources/SerializationToolbox.cpp
- OrthancFramework/Sources/SerializationToolbox.h
- OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp
- OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp
- OrthancFramework/UnitTestsSources/JobsTests.cpp
- OrthancServer/CMakeLists.txt
- OrthancServer/Resources/Configuration.json
- + OrthancServer/Resources/VersionScriptOrthanc.map
- OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp
- OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp
- OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp
- OrthancServer/Sources/main.cpp
Changes:
=====================================
.hg_archival.txt
=====================================
@@ -1,6 +1,6 @@
repo: 3959d33612ccaadc0d4d707227fbed09ac35e5fe
-node: b1319a5304fb494e567e32dc53aa11a6978eb554
-branch: Orthanc-1.9.6
+node: 6a6d2a34a34ef6442d08a122a635e9d14f870a5a
+branch: Orthanc-1.9.7
latesttag: toa2020012703
-latesttagdistance: 928
-changessincelatesttag: 1093
+latesttagdistance: 950
+changessincelatesttag: 1117
=====================================
CITATION.cff
=====================================
@@ -0,0 +1,14 @@
+cff-version: "1.1.0"
+message: "If you use this software, please cite it using these metadata."
+title: Orthanc
+abstract: "Orthanc is a lightweight open-source DICOM server for medical imaging supporting representational state transfer (REST)."
+authors:
+ -
+ affiliation: UCLouvain
+ family-names: Jodogne
+ given-names: "Sébastien"
+doi: "10.1007/s10278-018-0082-y"
+license: "GPL-3.0-or-later"
+repository-code: "https://hg.orthanc-server.com/orthanc/"
+version: 1.9.7
+date-released: 2021-08-31
=====================================
NEWS
=====================================
@@ -2,6 +2,32 @@ Pending changes in the mainline
===============================
+Version 1.9.7 (2021-08-31)
+==========================
+
+General
+-------
+
+* New configuration option "DicomAlwaysAllowMove" to disable verification of
+ the remote modality in C-MOVE SCP
+
+REST API
+--------
+
+* API version upgraded to 15
+* Added "Level" option to POST /tools/bulk-modify
+* Added missing OpenAPI documentation of "KeepSource" in ".../modify" and ".../anonymize"
+
+Maintenance
+-----------
+
+* Added file CITATION.cff
+* Linux Standard Base (LSB) builds of Orthanc can load non-LSB builds of plugins
+* Fix upload of ZIP archives containing a DICOMDIR file
+* Fix computation of the estimated time of arrival in jobs
+* Support detection of windowing and rescale in Philips multiframe images
+
+
Version 1.9.6 (2021-07-21)
==========================
=====================================
OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake
=====================================
@@ -134,6 +134,8 @@ if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" OR
set(ORTHANC_FRAMEWORK_MD5 "6d5ca4a73ac7d42445041ca79de1624d")
elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.5")
set(ORTHANC_FRAMEWORK_MD5 "10fc64de1254a095e5d3ed3931f0cfbb")
+ elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.6")
+ set(ORTHANC_FRAMEWORK_MD5 "4b5d05683d747c29b2860ad79d11e62e")
# Below this point are development snapshots that were used to
# release some plugin, before an official release of the Orthanc
=====================================
OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake
=====================================
@@ -23,7 +23,7 @@
#####################################################################
# Version of the build, should always be "mainline" except in release branches
-set(ORTHANC_VERSION "1.9.6")
+set(ORTHANC_VERSION "1.9.7")
# Version of the database schema. History:
# * Orthanc 0.1.0 -> Orthanc 0.3.0 = no versioning
@@ -37,7 +37,7 @@ set(ORTHANC_DATABASE_VERSION 6)
# Version of the Orthanc API, can be retrieved from "/system" URI in
# order to check whether new URI endpoints are available even if using
# the mainline version of Orthanc
-set(ORTHANC_API_VERSION "14")
+set(ORTHANC_API_VERSION "15")
#####################################################################
=====================================
OrthancFramework/Sources/DicomFormat/DicomTag.h
=====================================
@@ -172,6 +172,9 @@ namespace Orthanc
static const DicomTag DICOM_TAG_PATIENT_SPECIES_DESCRIPTION(0x0010, 0x2201);
static const DicomTag DICOM_TAG_STUDY_COMMENTS(0x0032, 0x4000);
static const DicomTag DICOM_TAG_OTHER_PATIENT_IDS(0x0010, 0x1000);
+ static const DicomTag DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE(0x5200, 0x9230);
+ static const DicomTag DICOM_TAG_PIXEL_VALUE_TRANSFORMATION_SEQUENCE(0x0028, 0x9145);
+ static const DicomTag DICOM_TAG_FRAME_VOI_LUT_SEQUENCE(0x0028, 0x9132);
// Tags used within the Stone of Orthanc
static const DicomTag DICOM_TAG_FRAME_INCREMENT_POINTER(0x0028, 0x0009);
=====================================
OrthancFramework/Sources/DicomFormat/DicomValue.cpp
=====================================
@@ -104,151 +104,115 @@ namespace Orthanc
}
#endif
- // same as ParseValue but in case the value actually contains a sequence,
- // it will return the first value
- // this has been introduced to support invalid "width/height" DICOM tags in some US
- // images where the width is stored as "800\0" !
- template <typename T,
- bool allowSigned>
- static bool ParseFirstValue(T& result,
- const DicomValue& source)
+ bool DicomValue::ParseInteger32(int32_t& result) const
{
- if (source.IsBinary() ||
- source.IsNull())
+ if (IsBinary() ||
+ IsNull())
{
return false;
}
-
- try
- {
- std::string value = Toolbox::StripSpaces(source.GetContent());
- if (value.empty())
- {
- return false;
- }
-
- if (!allowSigned &&
- value[0] == '-')
- {
- return false;
- }
-
- if (value.find("\\") == std::string::npos)
- {
- result = boost::lexical_cast<T>(value);
- return true;
- }
- else
- {
- std::vector<std::string> tokens;
- Toolbox::TokenizeString(tokens, value, '\\');
-
- if (tokens.size() >= 1)
- {
- result = boost::lexical_cast<T>(tokens[0]);
- return true;
- }
-
- return false;
- }
- }
- catch (boost::bad_lexical_cast&)
+ else
{
- return false;
+ return SerializationToolbox::ParseInteger32(result, GetContent());
}
}
-
- template <typename T,
- bool allowSigned>
- static bool ParseValue(T& result,
- const DicomValue& source)
+ bool DicomValue::ParseInteger64(int64_t& result) const
{
- if (source.IsBinary() ||
- source.IsNull())
+ if (IsBinary() ||
+ IsNull())
{
return false;
}
-
- try
- {
- std::string value = Toolbox::StripSpaces(source.GetContent());
- if (value.empty())
- {
- return false;
- }
-
- if (!allowSigned &&
- value[0] == '-')
- {
- return false;
- }
-
- result = boost::lexical_cast<T>(value);
- return true;
- }
- catch (boost::bad_lexical_cast&)
+ else
{
- return false;
+ return SerializationToolbox::ParseInteger64(result, GetContent());
}
}
- bool DicomValue::ParseInteger32(int32_t& result) const
+ bool DicomValue::ParseUnsignedInteger32(uint32_t& result) const
{
- int64_t tmp;
- if (ParseValue<int64_t, true>(tmp, *this))
+ if (IsBinary() ||
+ IsNull())
{
- result = static_cast<int32_t>(tmp);
- return (tmp == static_cast<int64_t>(result)); // Check no overflow occurs
+ return false;
}
else
{
- return false;
+ return SerializationToolbox::ParseUnsignedInteger32(result, GetContent());
}
}
- bool DicomValue::ParseInteger64(int64_t& result) const
- {
- return ParseValue<int64_t, true>(result, *this);
- }
-
- bool DicomValue::ParseUnsignedInteger32(uint32_t& result) const
+ bool DicomValue::ParseUnsignedInteger64(uint64_t& result) const
{
- uint64_t tmp;
- if (ParseValue<uint64_t, false>(tmp, *this))
+ if (IsBinary() ||
+ IsNull())
{
- result = static_cast<uint32_t>(tmp);
- return (tmp == static_cast<uint64_t>(result)); // Check no overflow occurs
+ return false;
}
else
{
- return false;
+ return SerializationToolbox::ParseUnsignedInteger64(result, GetContent());
}
}
- bool DicomValue::ParseUnsignedInteger64(uint64_t& result) const
- {
- return ParseValue<uint64_t, false>(result, *this);
- }
-
bool DicomValue::ParseFloat(float& result) const
{
- return ParseValue<float, true>(result, *this);
+ if (IsBinary() ||
+ IsNull())
+ {
+ return false;
+ }
+ else
+ {
+ return SerializationToolbox::ParseFloat(result, GetContent());
+ }
}
bool DicomValue::ParseDouble(double& result) const
{
- return ParseValue<double, true>(result, *this);
+ if (IsBinary() ||
+ IsNull())
+ {
+ return false;
+ }
+ else
+ {
+ return SerializationToolbox::ParseDouble(result, GetContent());
+ }
}
bool DicomValue::ParseFirstFloat(float& result) const
{
- return ParseFirstValue<float, true>(result, *this);
+ if (IsBinary() ||
+ IsNull())
+ {
+ return false;
+ }
+ else
+ {
+ return SerializationToolbox::ParseFirstFloat(result, GetContent());
+ }
}
bool DicomValue::ParseFirstUnsignedInteger(unsigned int& result) const
{
- return ParseFirstValue<unsigned int, true>(result, *this);
+ uint64_t value;
+
+ if (IsBinary() ||
+ IsNull())
+ {
+ return false;
+ }
+ else if (SerializationToolbox::ParseFirstUnsignedInteger64(value, GetContent()))
+ {
+ result = static_cast<unsigned int>(value);
+ return (static_cast<uint64_t>(result) == value); // Check no overflow
+ }
+ else
+ {
+ return false;
+ }
}
bool DicomValue::CopyToString(std::string& result,
=====================================
OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp
=====================================
@@ -2978,10 +2978,10 @@ namespace Orthanc
}
- static void ApplyInternal(FromDcmtkBridge::IDicomPathVisitor& visitor,
- DcmItem& item,
- const DicomPath& pattern,
- const DicomPath& actualPath)
+ void FromDcmtkBridge::IDicomPathVisitor::ApplyInternal(FromDcmtkBridge::IDicomPathVisitor& visitor,
+ DcmItem& item,
+ const DicomPath& pattern,
+ const DicomPath& actualPath)
{
const size_t level = actualPath.GetPrefixLength();
@@ -3020,9 +3020,9 @@ namespace Orthanc
}
- void FromDcmtkBridge::Apply(IDicomPathVisitor& visitor,
- DcmDataset& dataset,
- const DicomPath& path)
+ void FromDcmtkBridge::IDicomPathVisitor::Apply(IDicomPathVisitor& visitor,
+ DcmDataset& dataset,
+ const DicomPath& path)
{
DicomPath actualPath(path.GetFinalTag());
ApplyInternal(visitor, dataset, path, actualPath);
@@ -3044,7 +3044,7 @@ namespace Orthanc
};
Visitor visitor;
- Apply(visitor, dataset, path);
+ IDicomPathVisitor::Apply(visitor, dataset, path);
}
@@ -3084,7 +3084,7 @@ namespace Orthanc
};
Visitor visitor(onlyIfExists);
- Apply(visitor, dataset, path);
+ IDicomPathVisitor::Apply(visitor, dataset, path);
}
@@ -3159,9 +3159,59 @@ namespace Orthanc
else
{
Visitor visitor(element, mode);
- Apply(visitor, dataset, path);
+ IDicomPathVisitor::Apply(visitor, dataset, path);
}
}
+
+
+ bool FromDcmtkBridge::LookupSequenceItem(DicomMap& target,
+ DcmDataset& dataset,
+ const DicomPath& path,
+ size_t sequenceIndex)
+ {
+ class Visitor : public FromDcmtkBridge::IDicomPathVisitor
+ {
+ private:
+ bool found_;
+ DicomMap& target_;
+ size_t sequenceIndex_;
+
+ public:
+ Visitor(DicomMap& target,
+ size_t sequenceIndex) :
+ found_(false),
+ target_(target),
+ sequenceIndex_(sequenceIndex)
+ {
+ }
+
+ virtual void Visit(DcmItem& item,
+ const DicomPath& path) ORTHANC_OVERRIDE
+ {
+ DcmTagKey tag(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement());
+
+ DcmSequenceOfItems *sequence = NULL;
+
+ if (item.findAndGetSequence(tag, sequence).good() &&
+ sequence != NULL &&
+ sequenceIndex_ < sequence->card())
+ {
+ std::set<DicomTag> ignoreTagLength;
+ ExtractDicomSummary(target_, *sequence->getItem(sequenceIndex_), 0, ignoreTagLength);
+ found_ = true;
+ }
+ }
+
+ bool HasFound() const
+ {
+ return found_;
+ }
+ };
+
+ Visitor visitor(target, sequenceIndex);
+ IDicomPathVisitor::Apply(visitor, dataset, path);
+ return visitor.HasFound();
+ }
}
=====================================
OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h
=====================================
@@ -64,6 +64,12 @@ namespace Orthanc
// New in Orthanc 1.9.4
class ORTHANC_PUBLIC IDicomPathVisitor : public boost::noncopyable
{
+ private:
+ static void ApplyInternal(FromDcmtkBridge::IDicomPathVisitor& visitor,
+ DcmItem& item,
+ const DicomPath& pattern,
+ const DicomPath& actualPath);
+
public:
virtual ~IDicomPathVisitor()
{
@@ -71,6 +77,10 @@ namespace Orthanc
virtual void Visit(DcmItem& item,
const DicomPath& path) = 0;
+
+ static void Apply(IDicomPathVisitor& visitor,
+ DcmDataset& dataset,
+ const DicomPath& path);
};
@@ -256,10 +266,6 @@ namespace Orthanc
static void LogMissingTagsForStore(DcmDataset& dicom);
- static void Apply(IDicomPathVisitor& visitor,
- DcmDataset& dataset,
- const DicomPath& path);
-
static void RemovePath(DcmDataset& dataset,
const DicomPath& path);
@@ -271,5 +277,10 @@ namespace Orthanc
const DicomPath& path,
const DcmElement& element,
DicomReplaceMode mode);
+
+ static bool LookupSequenceItem(DicomMap& target,
+ DcmDataset& dataset,
+ const DicomPath& path,
+ size_t sequenceIndex);
};
}
=====================================
OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp
=====================================
@@ -78,6 +78,7 @@
#include "../Images/PamReader.h"
#include "../Logging.h"
#include "../OrthancException.h"
+#include "../SerializationToolbox.h"
#include "../Toolbox.h"
#if ORTHANC_SANDBOXED == 0
@@ -1777,6 +1778,117 @@ namespace Orthanc
}
+ bool ParsedDicomFile::LookupSequenceItem(DicomMap& target,
+ const DicomPath& path,
+ size_t sequenceIndex) const
+ {
+ DcmDataset& dataset = *const_cast<ParsedDicomFile&>(*this).GetDcmtkObject().getDataset();
+ return FromDcmtkBridge::LookupSequenceItem(target, dataset, path, sequenceIndex);
+ }
+
+
+ void ParsedDicomFile::GetDefaultWindowing(double& windowCenter,
+ double& windowWidth,
+ unsigned int frame) const
+ {
+ DcmDataset& dataset = *const_cast<ParsedDicomFile&>(*this).GetDcmtkObject().getDataset();
+
+ const char* wc = NULL;
+ const char* ww = NULL;
+ DcmItem *item1 = NULL;
+ DcmItem *item2 = NULL;
+
+ if (dataset.findAndGetString(DCM_WindowCenter, wc).good() &&
+ dataset.findAndGetString(DCM_WindowWidth, ww).good() &&
+ wc != NULL &&
+ ww != NULL &&
+ SerializationToolbox::ParseFirstDouble(windowCenter, wc) &&
+ SerializationToolbox::ParseFirstDouble(windowWidth, ww))
+ {
+ return; // OK
+ }
+ else if (dataset.findAndGetSequenceItem(DCM_PerFrameFunctionalGroupsSequence, item1, frame).good() &&
+ item1 != NULL &&
+ item1->findAndGetSequenceItem(DCM_FrameVOILUTSequence, item2, 0).good() &&
+ item2 != NULL &&
+ item2->findAndGetString(DCM_WindowCenter, wc).good() &&
+ item2->findAndGetString(DCM_WindowWidth, ww).good() &&
+ wc != NULL &&
+ ww != NULL &&
+ SerializationToolbox::ParseFirstDouble(windowCenter, wc) &&
+ SerializationToolbox::ParseFirstDouble(windowWidth, ww))
+ {
+ // New in Orthanc 1.9.7, to deal with Philips multiframe images
+ // (cf. private mail from Tomas Kenda on 2021-08-17)
+ return; // OK
+ }
+ else
+ {
+ Uint16 bitsStored = 0;
+ if (!dataset.findAndGetUint16(DCM_BitsStored, bitsStored).good() ||
+ bitsStored == 0)
+ {
+ bitsStored = 8; // Rough assumption
+ }
+
+ windowWidth = static_cast<double>(1 << bitsStored);
+ windowCenter = windowWidth / 2.0f;
+ }
+ }
+
+
+ void ParsedDicomFile::GetRescale(double& rescaleIntercept,
+ double& rescaleSlope,
+ unsigned int frame) const
+ {
+ DcmDataset& dataset = *const_cast<ParsedDicomFile&>(*this).GetDcmtkObject().getDataset();
+
+ const char* sopClassUid = NULL;
+ const char* intercept = NULL;
+ const char* slope = NULL;
+ DcmItem *item1 = NULL;
+ DcmItem *item2 = NULL;
+
+ if (dataset.findAndGetString(DCM_SOPClassUID, sopClassUid).good() &&
+ sopClassUid != NULL &&
+ std::string(sopClassUid) == std::string(UID_RTDoseStorage))
+ {
+ // We must not take the rescale value into account in the case of doses
+ rescaleIntercept = 0;
+ rescaleSlope = 1;
+ }
+ else if (dataset.findAndGetString(DCM_RescaleIntercept, intercept).good() &&
+ dataset.findAndGetString(DCM_RescaleSlope, slope).good() &&
+ intercept != NULL &&
+ slope != NULL &&
+ SerializationToolbox::ParseDouble(rescaleIntercept, intercept) &&
+ SerializationToolbox::ParseDouble(rescaleSlope, slope))
+ {
+ return; // OK
+ }
+ else if (dataset.findAndGetSequenceItem(DCM_PerFrameFunctionalGroupsSequence, item1, frame).good() &&
+ item1 != NULL &&
+ item1->findAndGetSequenceItem(DCM_PixelValueTransformationSequence, item2, 0).good() &&
+ item2 != NULL &&
+ item2->findAndGetString(DCM_RescaleIntercept, intercept).good() &&
+ item2->findAndGetString(DCM_RescaleSlope, slope).good() &&
+ intercept != NULL &&
+ slope != NULL &&
+ SerializationToolbox::ParseDouble(rescaleIntercept, intercept) &&
+ SerializationToolbox::ParseDouble(rescaleSlope, slope))
+ {
+ // New in Orthanc 1.9.7, to deal with Philips multiframe images
+ // (cf. private mail from Tomas Kenda on 2021-08-17)
+ return; // OK
+ }
+ else
+ {
+ rescaleIntercept = 0;
+ rescaleSlope = 1;
+ }
+ }
+
+
#if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1
// Alias for binary compatibility with Orthanc Framework 1.7.2 => don't use it anymore
void ParsedDicomFile::DatasetToJson(Json::Value& target,
=====================================
OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h
=====================================
@@ -285,5 +285,17 @@ namespace Orthanc
void ClearPath(const DicomPath& path,
bool onlyIfExists);
+
+ bool LookupSequenceItem(DicomMap& target,
+ const DicomPath& path,
+ size_t sequenceIndex) const;
+
+ void GetDefaultWindowing(double& windowCenter,
+ double& windowWidth,
+ unsigned int frame) const;
+
+ void GetRescale(double& rescaleIntercept,
+ double& rescaleSlope,
+ unsigned int frame) const;
};
}
=====================================
OrthancFramework/Sources/Images/ImageProcessing.cpp
=====================================
@@ -1405,6 +1405,14 @@ namespace Orthanc
}
+ static bool IsIdentityRescaling(float offset,
+ float scaling)
+ {
+ return (std::abs(offset) <= 10.0f * std::numeric_limits<float>::epsilon() &&
+ std::abs(scaling - 1.0f) <= 10.0f * std::numeric_limits<float>::epsilon());
+ }
+
+
void ImageProcessing::ShiftScale2(ImageAccessor& image,
float offset,
float scaling,
@@ -1413,6 +1421,11 @@ namespace Orthanc
// We compute "a * x + b"
const float a = scaling;
const float b = offset;
+
+ if (IsIdentityRescaling(offset, scaling))
+ {
+ return;
+ }
switch (image.GetFormat())
{
@@ -1477,6 +1490,13 @@ namespace Orthanc
const float a = scaling;
const float b = offset;
+ if (target.GetFormat() == source.GetFormat() &&
+ IsIdentityRescaling(offset, scaling))
+ {
+ Copy(target, source);
+ return;
+ }
+
switch (target.GetFormat())
{
case PixelFormat_Grayscale8:
=====================================
OrthancFramework/Sources/JobsEngine/JobInfo.cpp
=====================================
@@ -70,8 +70,8 @@ namespace Orthanc
if (status_.GetProgress() > 0.01f &&
ms > 0.01f)
{
- float ratio = static_cast<float>(1.0 - status_.GetProgress());
- long long remaining = boost::math::llround(ratio * ms);
+ float progress = status_.GetProgress();
+ long long remaining = boost::math::llround(ms / progress * (1.0f - progress));
eta_ = timestamp_ + boost::posix_time::milliseconds(remaining);
hasEta_ = true;
}
=====================================
OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp
=====================================
@@ -226,6 +226,11 @@ namespace Orthanc
return runtime_;
}
+ void ResetRuntime()
+ {
+ runtime_ = boost::posix_time::milliseconds(0);
+ }
+
const JobStatus& GetLastStatus() const
{
return lastStatus_;
@@ -1071,6 +1076,7 @@ namespace Orthanc
(void) ok; // Remove warning about unused variable in release builds
assert(ok);
+ found->second->ResetRuntime();
found->second->SetState(JobState_Pending);
pendingJobs_.push(found->second);
pendingJobAvailable_.notify_one();
=====================================
OrthancFramework/Sources/SerializationToolbox.cpp
=====================================
@@ -24,11 +24,15 @@
#include "SerializationToolbox.h"
#include "OrthancException.h"
+#include "Toolbox.h"
#if ORTHANC_ENABLE_DCMTK == 1
# include "DicomParsing/FromDcmtkBridge.h"
#endif
+#include <boost/lexical_cast.hpp>
+
+
namespace Orthanc
{
static bool ParseTagInternal(DicomTag& tag,
@@ -445,4 +449,202 @@ namespace Orthanc
value[it->first.Format()] = it->second;
}
}
+
+
+ template <typename T,
+ bool allowSigned>
+ static bool ParseValue(T& target,
+ const std::string& source)
+ {
+ try
+ {
+ std::string value = Toolbox::StripSpaces(source);
+ if (value.empty())
+ {
+ return false;
+ }
+ else if (!allowSigned &&
+ value[0] == '-')
+ {
+ return false;
+ }
+ else
+ {
+ target = boost::lexical_cast<T>(value);
+ return true;
+ }
+ }
+ catch (boost::bad_lexical_cast&)
+ {
+ return false;
+ }
+ }
+
+
+ bool SerializationToolbox::ParseInteger32(int32_t& target,
+ const std::string& source)
+ {
+ int64_t tmp;
+ if (ParseValue<int64_t, true>(tmp, source))
+ {
+ target = static_cast<int32_t>(tmp);
+ return (tmp == static_cast<int64_t>(target)); // Check no overflow occurs
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+ bool SerializationToolbox::ParseInteger64(int64_t& target,
+ const std::string& source)
+ {
+ return ParseValue<int64_t, true>(target, source);
+ }
+
+
+ bool SerializationToolbox::ParseUnsignedInteger32(uint32_t& target,
+ const std::string& source)
+ {
+ uint64_t tmp;
+ if (ParseValue<uint64_t, false>(tmp, source))
+ {
+ target = static_cast<uint32_t>(tmp);
+ return (tmp == static_cast<uint64_t>(target)); // Check no overflow occurs
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+ bool SerializationToolbox::ParseUnsignedInteger64(uint64_t& target,
+ const std::string& source)
+ {
+ return ParseValue<uint64_t, false>(target, source);
+ }
+
+
+ bool SerializationToolbox::ParseFloat(float& target,
+ const std::string& source)
+ {
+ return ParseValue<float, true>(target, source);
+ }
+
+
+ bool SerializationToolbox::ParseDouble(double& target,
+ const std::string& source)
+ {
+ return ParseValue<double, true>(target, source);
+ }
+
+
+ static bool GetFirstItem(std::string& target,
+ const std::string& source)
+ {
+ std::vector<std::string> tokens;
+ Toolbox::TokenizeString(tokens, source, '\\');
+
+ if (tokens.empty())
+ {
+ return false;
+ }
+ else
+ {
+ target = tokens[0];
+ return true;
+ }
+ }
+
+
+ bool SerializationToolbox::ParseFirstInteger32(int32_t& target,
+ const std::string& source)
+ {
+ std::string first;
+ if (GetFirstItem(first, source))
+ {
+ return ParseInteger32(target, first);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+ bool SerializationToolbox::ParseFirstInteger64(int64_t& target,
+ const std::string& source)
+ {
+ std::string first;
+ if (GetFirstItem(first, source))
+ {
+ return ParseInteger64(target, first);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+ bool SerializationToolbox::ParseFirstUnsignedInteger32(uint32_t& target,
+ const std::string& source)
+ {
+ std::string first;
+ if (GetFirstItem(first, source))
+ {
+ return ParseUnsignedInteger32(target, first);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+ bool SerializationToolbox::ParseFirstUnsignedInteger64(uint64_t& target,
+ const std::string& source)
+ {
+ std::string first;
+ if (GetFirstItem(first, source))
+ {
+ return ParseUnsignedInteger64(target, first);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+ bool SerializationToolbox::ParseFirstFloat(float& target,
+ const std::string& source)
+ {
+ std::string first;
+ if (GetFirstItem(first, source))
+ {
+ return ParseFloat(target, first);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+ bool SerializationToolbox::ParseFirstDouble(double& target,
+ const std::string& source)
+ {
+ std::string first;
+ if (GetFirstItem(first, source))
+ {
+ return ParseDouble(target, first);
+ }
+ else
+ {
+ return false;
+ }
+ }
}
=====================================
OrthancFramework/Sources/SerializationToolbox.h
=====================================
@@ -101,5 +101,41 @@ namespace Orthanc
static void WriteMapOfTags(Json::Value& target,
const std::map<DicomTag, std::string>& values,
const std::string& field);
+
+ static bool ParseInteger32(int32_t& result,
+ const std::string& value);
+
+ static bool ParseInteger64(int64_t& result,
+ const std::string& value);
+
+ static bool ParseUnsignedInteger32(uint32_t& result,
+ const std::string& value);
+
+ static bool ParseUnsignedInteger64(uint64_t& result,
+ const std::string& value);
+
+ static bool ParseFloat(float& result,
+ const std::string& value);
+
+ static bool ParseDouble(double& result,
+ const std::string& value);
+
+ static bool ParseFirstInteger32(int32_t& result,
+ const std::string& value);
+
+ static bool ParseFirstInteger64(int64_t& result,
+ const std::string& value);
+
+ static bool ParseFirstUnsignedInteger32(uint32_t& result,
+ const std::string& value);
+
+ static bool ParseFirstUnsignedInteger64(uint64_t& result,
+ const std::string& value);
+
+ static bool ParseFirstFloat(float& result,
+ const std::string& value);
+
+ static bool ParseFirstDouble(double& result,
+ const std::string& value);
};
}
=====================================
OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp
=====================================
@@ -2739,6 +2739,30 @@ TEST(ParsedDicomFile, DicomPath)
ASSERT_EQ("1.2.840.113619.2.176.2025.1499492.7040.1171286241.726", vv[REF_IM_SEQ][1][REF_SOP_INSTANCE].asString()); // kept
ASSERT_EQ("1.2.840.113704.1.111.7016.1342451220.40", vv[REL_SERIES_SEQ][0][STUDY_INSTANCE_UID].asString()); // kept
}
+
+ {
+ std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+
+ DicomMap m;
+ ASSERT_TRUE(dicom->LookupSequenceItem(m, DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE), 0));
+ ASSERT_EQ(2u, m.GetSize());
+ ASSERT_EQ("1.2.840.113619.2.176.2025.1499492.7040.1171286241.719",
+ m.GetStringValue(DICOM_TAG_REFERENCED_SOP_INSTANCE_UID, "", false));
+
+ ASSERT_TRUE(dicom->LookupSequenceItem(m, DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE), 1));
+ ASSERT_EQ(2u, m.GetSize());
+ ASSERT_EQ("1.2.840.113619.2.176.2025.1499492.7040.1171286241.726",
+ m.GetStringValue(DICOM_TAG_REFERENCED_SOP_INSTANCE_UID, "", false));
+
+ ASSERT_FALSE(dicom->LookupSequenceItem(m, DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE), 2));
+
+ ASSERT_TRUE(dicom->LookupSequenceItem(m, DicomPath(DicomTag(0x0008, 0x1250), 0, DicomTag(0x0040, 0xa170)), 0));
+ ASSERT_EQ(2u, m.GetSize());
+ ASSERT_EQ("122403", m.GetStringValue(DicomTag(0x0008, 0x0100), "", false));
+ ASSERT_EQ("WORLD", m.GetStringValue(DICOM_TAG_SERIES_DESCRIPTION, "", false));
+
+ ASSERT_FALSE(dicom->LookupSequenceItem(m, DicomPath(DicomTag(0x0008, 0x1250), 0, DicomTag(0x0040, 0xa170)), 1));
+ }
}
@@ -2995,6 +3019,121 @@ TEST(FromDcmtkBridge, VisitorRemoveTag)
+TEST(ParsedDicomFile, ImageInformation)
+{
+ double wc, ww;
+ double ri, rs;
+ PhotometricInterpretation p;
+
+ {
+ ParsedDicomFile dicom(false);
+ dicom.GetDefaultWindowing(wc, ww, 5);
+ dicom.GetRescale(ri, rs, 5);
+ ASSERT_DOUBLE_EQ(128.0, wc);
+ ASSERT_DOUBLE_EQ(256.0, ww);
+ ASSERT_FALSE(dicom.LookupPhotometricInterpretation(p));
+ ASSERT_DOUBLE_EQ(0.0, ri);
+ ASSERT_DOUBLE_EQ(1.0, rs);
+ }
+
+ {
+ ParsedDicomFile dicom(false);
+ ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_BitsStored, "4").good());
+ ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_PhotometricInterpretation, "RGB").good());
+ dicom.GetDefaultWindowing(wc, ww, 5);
+ ASSERT_DOUBLE_EQ(8.0, wc);
+ ASSERT_DOUBLE_EQ(16.0, ww);
+ ASSERT_TRUE(dicom.LookupPhotometricInterpretation(p));
+ ASSERT_EQ(PhotometricInterpretation_RGB, p);
+ }
+
+ {
+ ParsedDicomFile dicom(false);
+ ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_WindowCenter, "12").good());
+ ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_WindowWidth, "-22").good());
+ ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_RescaleIntercept, "-22").good());
+ ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_RescaleSlope, "-23").good());
+ dicom.GetDefaultWindowing(wc, ww, 5);
+ dicom.GetRescale(ri, rs, 5);
+ ASSERT_DOUBLE_EQ(12.0, wc);
+ ASSERT_DOUBLE_EQ(-22.0, ww);
+ ASSERT_DOUBLE_EQ(-22.0, ri);
+ ASSERT_DOUBLE_EQ(-23.0, rs);
+ }
+
+ {
+ ParsedDicomFile dicom(false);
+ ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_WindowCenter, "12\\13\\14").good());
+ ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_WindowWidth, "-22\\-23\\-24").good());
+ ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_RescaleIntercept, "32\\33\\34").good());
+ ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_RescaleSlope, "-42\\-43\\-44").good());
+ dicom.GetDefaultWindowing(wc, ww, 5);
+ dicom.GetRescale(ri, rs, 5);
+ ASSERT_DOUBLE_EQ(12.0, wc);
+ ASSERT_DOUBLE_EQ(-22.0, ww);
+ ASSERT_DOUBLE_EQ(0.0, ri);
+ ASSERT_DOUBLE_EQ(1.0, rs);
+ }
+
+ {
+ // Philips multiframe
+ Json::Value v = Json::objectValue;
+ v["PerFrameFunctionalGroupsSequence"][0]["FrameVOILUTSequence"][0]["WindowCenter"] = "614";
+ v["PerFrameFunctionalGroupsSequence"][0]["FrameVOILUTSequence"][0]["WindowWidth"] = "1067";
+ v["PerFrameFunctionalGroupsSequence"][0]["PixelValueTransformationSequence"][0]["RescaleIntercept"] = "12";
+ v["PerFrameFunctionalGroupsSequence"][0]["PixelValueTransformationSequence"][0]["RescaleSlope"] = "2.551648";
+ v["PerFrameFunctionalGroupsSequence"][1]["FrameVOILUTSequence"][0]["WindowCenter"] = "-61";
+ v["PerFrameFunctionalGroupsSequence"][1]["FrameVOILUTSequence"][0]["WindowWidth"] = "-63";
+ v["PerFrameFunctionalGroupsSequence"][1]["PixelValueTransformationSequence"][0]["RescaleIntercept"] = "13";
+ v["PerFrameFunctionalGroupsSequence"][1]["PixelValueTransformationSequence"][0]["RescaleSlope"] = "-14";
+ std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+
+ dicom->GetDefaultWindowing(wc, ww, 0);
+ dicom->GetRescale(ri, rs, 0);
+ ASSERT_DOUBLE_EQ(614.0, wc);
+ ASSERT_DOUBLE_EQ(1067.0, ww);
+ ASSERT_DOUBLE_EQ(12.0, ri);
+ ASSERT_DOUBLE_EQ(2.551648, rs);
+
+ dicom->GetDefaultWindowing(wc, ww, 1);
+ dicom->GetRescale(ri, rs, 1);
+ ASSERT_DOUBLE_EQ(-61.0, wc);
+ ASSERT_DOUBLE_EQ(-63.0, ww);
+ ASSERT_DOUBLE_EQ(13.0, ri);
+ ASSERT_DOUBLE_EQ(-14.0, rs);
+
+ dicom->GetDefaultWindowing(wc, ww, 2);
+ dicom->GetRescale(ri, rs, 2);
+ ASSERT_DOUBLE_EQ(128.0, wc);
+ ASSERT_DOUBLE_EQ(256.0, ww);
+ ASSERT_DOUBLE_EQ(0.0, ri);
+ ASSERT_DOUBLE_EQ(1.0, rs);
+ }
+
+ {
+ // RT-DOSE
+ Json::Value v = Json::objectValue;
+ v["RescaleIntercept"] = "10";
+ v["RescaleSlope"] = "20";
+ v["PerFrameFunctionalGroupsSequence"][0]["PixelValueTransformationSequence"][0]["RescaleIntercept"] = "30";
+ v["PerFrameFunctionalGroupsSequence"][0]["PixelValueTransformationSequence"][0]["RescaleSlope"] = "40";
+ std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+
+ dicom->GetRescale(ri, rs, 0);
+ ASSERT_DOUBLE_EQ(10.0, ri);
+ ASSERT_DOUBLE_EQ(20.0, rs);
+
+ v["SOPClassUID"] = "1.2.840.10008.5.1.4.1.1.481.2";
+ dicom.reset(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+ dicom->GetRescale(ri, rs, 0);
+ ASSERT_DOUBLE_EQ(0.0, ri);
+ ASSERT_DOUBLE_EQ(1.0, rs);
+ }
+}
+
+
+
+
#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
#include "../Sources/DicomNetworking/DicomStoreUserConnection.h"
=====================================
OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp
=====================================
@@ -975,6 +975,25 @@ TEST(ImageProcessing, ShiftScaleGrayscale8)
}
+TEST(ImageProcessing, Grayscale8_Identity)
+{
+ Image image(PixelFormat_Float32, 5, 1, false);
+ ImageTraits<PixelFormat_Float32>::SetPixel(image, 0, 0, 0);
+ ImageTraits<PixelFormat_Float32>::SetPixel(image, 2.5, 1, 0);
+ ImageTraits<PixelFormat_Float32>::SetPixel(image, 5.5, 2, 0);
+ ImageTraits<PixelFormat_Float32>::SetPixel(image, 10.5, 3, 0);
+ ImageTraits<PixelFormat_Float32>::SetPixel(image, 255.5, 4, 0);
+
+ Image image2(PixelFormat_Grayscale8, 5, 1, false);
+ ImageProcessing::ShiftScale(image2, image, 0, 1, false);
+ ASSERT_TRUE(TestGrayscale8Pixel(image2, 0, 0, 0));
+ ASSERT_TRUE(TestGrayscale8Pixel(image2, 1, 0, 2));
+ ASSERT_TRUE(TestGrayscale8Pixel(image2, 2, 0, 5));
+ ASSERT_TRUE(TestGrayscale8Pixel(image2, 3, 0, 10));
+ ASSERT_TRUE(TestGrayscale8Pixel(image2, 4, 0, 255));
+}
+
+
TEST(ImageProcessing, ShiftScaleGrayscale16)
{
Image image(PixelFormat_Grayscale16, 5, 1, false);
@@ -1011,6 +1030,24 @@ TEST(ImageProcessing, ShiftScaleSignedGrayscale16)
}
+TEST(ImageProcessing, ShiftScaleSignedGrayscale16_Identity)
+{
+ Image image(PixelFormat_SignedGrayscale16, 5, 1, false);
+ SetSignedGrayscale16Pixel(image, 0, 0, 0);
+ SetSignedGrayscale16Pixel(image, 1, 0, 2);
+ SetSignedGrayscale16Pixel(image, 2, 0, 5);
+ SetSignedGrayscale16Pixel(image, 3, 0, 10);
+ SetSignedGrayscale16Pixel(image, 4, 0, 255);
+
+ ImageProcessing::ShiftScale(image, 0, 1, true);
+ ASSERT_TRUE(TestSignedGrayscale16Pixel(image, 0, 0, 0));
+ ASSERT_TRUE(TestSignedGrayscale16Pixel(image, 1, 0, 2));
+ ASSERT_TRUE(TestSignedGrayscale16Pixel(image, 2, 0, 5));
+ ASSERT_TRUE(TestSignedGrayscale16Pixel(image, 3, 0, 10));
+ ASSERT_TRUE(TestSignedGrayscale16Pixel(image, 4, 0, 255));
+}
+
+
TEST(ImageProcessing, ShiftScale2)
{
std::vector<float> va;
=====================================
OrthancFramework/UnitTestsSources/JobsTests.cpp
=====================================
@@ -1575,3 +1575,86 @@ TEST(JobsSerialization, DicomAssociationParameters)
ASSERT_FALSE(b.IsRemoteCertificateRequired());
}
}
+
+
+TEST(SerializationToolbox, Numbers)
+{
+ {
+ int32_t i;
+ ASSERT_FALSE(SerializationToolbox::ParseInteger32(i, ""));
+ ASSERT_FALSE(SerializationToolbox::ParseInteger32(i, "ee"));
+ ASSERT_TRUE(SerializationToolbox::ParseInteger32(i, "42")); ASSERT_EQ(42, i);
+ ASSERT_TRUE(SerializationToolbox::ParseInteger32(i, "-42")); ASSERT_EQ(-42, i);
+ ASSERT_TRUE(SerializationToolbox::ParseInteger32(i, "-2147483648")); ASSERT_EQ(-2147483648l, i);
+ ASSERT_TRUE(SerializationToolbox::ParseInteger32(i, "2147483647")); ASSERT_EQ(2147483647l, i);
+ ASSERT_FALSE(SerializationToolbox::ParseInteger32(i, "-2147483649"));
+ ASSERT_FALSE(SerializationToolbox::ParseInteger32(i, "2147483648"));
+ ASSERT_FALSE(SerializationToolbox::ParseInteger32(i, "-2\\-3\\-4"));
+ ASSERT_TRUE(SerializationToolbox::ParseFirstInteger32(i, "-2\\-3\\-4")); ASSERT_EQ(-2, i);
+ }
+
+ {
+ uint32_t i;
+ ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger32(i, ""));
+ ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger32(i, "ee"));
+ ASSERT_TRUE(SerializationToolbox::ParseUnsignedInteger32(i, "42")); ASSERT_EQ(42u, i);
+ ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger32(i, "-42"));
+ ASSERT_TRUE(SerializationToolbox::ParseUnsignedInteger32(i, "4294967295")); ASSERT_EQ(4294967295u, i);
+ ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger32(i, "4294967296"));
+ ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger32(i, "2\\3\\4"));
+ ASSERT_TRUE(SerializationToolbox::ParseFirstUnsignedInteger32(i, "2\\3\\4")); ASSERT_EQ(2u, i);
+ }
+
+ {
+ int64_t i;
+ ASSERT_FALSE(SerializationToolbox::ParseInteger64(i, ""));
+ ASSERT_FALSE(SerializationToolbox::ParseInteger64(i, "ee"));
+ ASSERT_TRUE(SerializationToolbox::ParseInteger64(i, "42")); ASSERT_EQ(42, i);
+ ASSERT_TRUE(SerializationToolbox::ParseInteger64(i, "-42")); ASSERT_EQ(-42, i);
+ ASSERT_TRUE(SerializationToolbox::ParseInteger64(i, "-2147483649")); ASSERT_EQ(-2147483649ll, i);
+ ASSERT_TRUE(SerializationToolbox::ParseInteger64(i, "2147483648")); ASSERT_EQ(2147483648ll, i);
+ ASSERT_FALSE(SerializationToolbox::ParseInteger64(i, "-2\\-3\\-4"));
+ ASSERT_TRUE(SerializationToolbox::ParseFirstInteger64(i, "-2\\-3\\-4")); ASSERT_EQ(-2, i);
+ }
+
+ {
+ uint64_t i;
+ ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger64(i, ""));
+ ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger64(i, "ee"));
+ ASSERT_TRUE(SerializationToolbox::ParseUnsignedInteger64(i, "42")); ASSERT_EQ(42u, i);
+ ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger64(i, "-42"));
+ ASSERT_TRUE(SerializationToolbox::ParseUnsignedInteger64(i, "4294967296")); ASSERT_EQ(4294967296lu, i);
+ ASSERT_FALSE(SerializationToolbox::ParseUnsignedInteger64(i, "2\\3\\4"));
+ ASSERT_TRUE(SerializationToolbox::ParseFirstUnsignedInteger64(i, "2\\3\\4")); ASSERT_EQ(2u, i);
+ }
+
+ {
+ float i;
+ ASSERT_FALSE(SerializationToolbox::ParseFloat(i, ""));
+ ASSERT_FALSE(SerializationToolbox::ParseFloat(i, "ee"));
+ ASSERT_TRUE(SerializationToolbox::ParseFloat(i, "42")); ASSERT_FLOAT_EQ(42.0f, i);
+ ASSERT_TRUE(SerializationToolbox::ParseFloat(i, "-42")); ASSERT_FLOAT_EQ(-42.0f, i);
+ ASSERT_FALSE(SerializationToolbox::ParseFloat(i, "2\\3\\4"));
+ ASSERT_TRUE(SerializationToolbox::ParseFirstFloat(i, "1.367\\2.367\\3.367")); ASSERT_FLOAT_EQ(1.367f, i);
+
+ ASSERT_TRUE(SerializationToolbox::ParseFloat(i, "1.2")); ASSERT_FLOAT_EQ(1.2f, i);
+ ASSERT_TRUE(SerializationToolbox::ParseFloat(i, "-1.2e+2")); ASSERT_FLOAT_EQ(-120.0f, i);
+ ASSERT_TRUE(SerializationToolbox::ParseFloat(i, "-1e-2")); ASSERT_FLOAT_EQ(-0.01f, i);
+ ASSERT_TRUE(SerializationToolbox::ParseFloat(i, "1.3671875")); ASSERT_FLOAT_EQ(1.3671875f, i);
+ }
+
+ {
+ double i;
+ ASSERT_FALSE(SerializationToolbox::ParseDouble(i, ""));
+ ASSERT_FALSE(SerializationToolbox::ParseDouble(i, "ee"));
+ ASSERT_TRUE(SerializationToolbox::ParseDouble(i, "42")); ASSERT_DOUBLE_EQ(42.0, i);
+ ASSERT_TRUE(SerializationToolbox::ParseDouble(i, "-42")); ASSERT_DOUBLE_EQ(-42.0, i);
+ ASSERT_FALSE(SerializationToolbox::ParseDouble(i, "2\\3\\4"));
+ ASSERT_TRUE(SerializationToolbox::ParseFirstDouble(i, "1.367\\2.367\\3.367")); ASSERT_DOUBLE_EQ(1.367, i);
+
+ ASSERT_TRUE(SerializationToolbox::ParseDouble(i, "1.2")); ASSERT_DOUBLE_EQ(1.2, i);
+ ASSERT_TRUE(SerializationToolbox::ParseDouble(i, "-1.2e+2")); ASSERT_DOUBLE_EQ(-120.0, i);
+ ASSERT_TRUE(SerializationToolbox::ParseDouble(i, "-1e-2")); ASSERT_DOUBLE_EQ(-0.01, i);
+ ASSERT_TRUE(SerializationToolbox::ParseDouble(i, "1.3671875")); ASSERT_DOUBLE_EQ(1.3671875, i);
+ }
+}
=====================================
OrthancServer/CMakeLists.txt
=====================================
@@ -397,6 +397,16 @@ add_executable(Orthanc
target_link_libraries(Orthanc ServerLibrary CoreLibrary ${DCMTK_LIBRARIES})
+if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+ # The link flag below hides all the global functions so that a Linux
+ # Standard Base (LSB) build of Orthanc can load plugins that are not
+ # built using LSB (new in Orthanc 1.9.7)
+ set_property(
+ TARGET Orthanc
+ PROPERTY LINK_FLAGS "-Wl,--version-script=${CMAKE_SOURCE_DIR}/Resources/VersionScriptOrthanc.map"
+ )
+endif()
+
install(
TARGETS Orthanc
RUNTIME DESTINATION sbin
=====================================
OrthancServer/Resources/Configuration.json
=====================================
@@ -291,6 +291,12 @@
// option to "true" implies security risks. (new in Orthanc 1.9.0)
"DicomAlwaysAllowGet" : false,
+ // Whether the Orthanc SCP allows incoming C-MOVE requests, even
+ // from SCU modalities it does not know about (i.e. that are not
+ // listed in the "DicomModalities" option above). Setting this
+ // option to "true" implies security risks. (new in Orthanc 1.9.7)
+ "DicomAlwaysAllowMove" : false,
+
// Whether Orthanc checks the IP/hostname address of the remote
// modality initiating a DICOM connection (as listed in the
// "DicomModalities" option above). If this option is set to
=====================================
OrthancServer/Resources/VersionScriptOrthanc.map
=====================================
@@ -0,0 +1,13 @@
+# This is a version-script for the main Orthanc binary, that hides all
+# the global functions of the executable so that a Linux Standard Base
+# (LSB) build of Orthanc can load plugins that are not built using
+# LSB. Otherwise, the dynamic loader of the plugins will try to use
+# the global functions published by the Orthanc server, which results
+# in a segmentation fault if the data structures don't have the same
+# memory layout (e.g. debug vs. release, or another version of some
+# C/C++ library used both by Orthanc, typically jsoncpp).
+
+{
+local:
+ *;
+};
=====================================
OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp
=====================================
@@ -58,6 +58,7 @@ static const char* const INTERPRET_BINARY_TAGS = "InterpretBinaryTags";
static const char* const KEEP = "Keep";
static const char* const KEEP_PRIVATE_TAGS = "KeepPrivateTags";
static const char* const KEEP_SOURCE = "KeepSource";
+static const char* const LEVEL = "Level";
static const char* const PARENT = "Parent";
static const char* const PRIVATE_CREATOR = "PrivateCreator";
static const char* const REMOVE = "Remove";
@@ -80,6 +81,15 @@ namespace Orthanc
}
+ static void DocumentKeepSource(RestApiPostCall& call)
+ {
+ call.GetDocumentation()
+ .SetRequestField(KEEP_SOURCE, RestApiCallDocumentation::Type_Boolean,
+ "If set to `false`, instructs Orthanc to the remove original resources. "
+ "By default, the original resources are kept in Orthanc.", false);
+ }
+
+
static void DocumentModifyOptions(RestApiPostCall& call)
{
// Check out "DicomModification::ParseModifyRequest()"
@@ -102,6 +112,9 @@ namespace Orthanc
"as this breaks the DICOM model of the real world.", false)
.SetRequestField(PRIVATE_CREATOR, RestApiCallDocumentation::Type_String,
"The private creator to be used for private tags in `Replace`", false);
+
+ // This was existing, but undocumented in Orthanc <= 1.9.6
+ DocumentKeepSource(call);
}
@@ -125,6 +138,9 @@ namespace Orthanc
"List of DICOM tags whose value must not be destroyed by the anonymization. " INFO_SUBSEQUENCES, false)
.SetRequestField(PRIVATE_CREATOR, RestApiCallDocumentation::Type_String,
"The private creator to be used for private tags in `Replace`", false);
+
+ // This was existing, but undocumented in Orthanc <= 1.9.6
+ DocumentKeepSource(call);
}
@@ -443,6 +459,11 @@ namespace Orthanc
.SetSummary("Modify a set of resources")
.SetRequestField(RESOURCES, RestApiCallDocumentation::Type_JsonListOfStrings,
"List of the Orthanc identifiers of the patients/studies/series/instances of interest.", true)
+ .SetRequestField(LEVEL, RestApiCallDocumentation::Type_String,
+ "Level of the modification (`Patient`, `Study`, `Series` or `Instance`). If absent, "
+ "the level defaults to `Instance`, but is set to `Patient` if `PatientID` is modified, "
+ "to `Study` if `StudyInstanceUID` is modified, or to `Series` if `SeriesInstancesUID` "
+ "is modified. (new in Orthanc 1.9.7)", false)
.SetDescription("Start a job that will modify all the DICOM patients, studies, series or instances "
"whose identifiers are provided in the `Resources` field.")
.AddAnswerType(MimeType_Json, "The list of all the resources that have been altered by this modification");
@@ -454,7 +475,15 @@ namespace Orthanc
Json::Value body;
ParseModifyRequest(body, *modification, call);
- modification->SetLevel(DetectModifyLevel(*modification));
+ if (body.isMember(LEVEL))
+ {
+ // This case was introduced in Orthanc 1.9.7
+ modification->SetLevel(StringToResourceType(body[LEVEL].asCString()));
+ }
+ else
+ {
+ modification->SetLevel(DetectModifyLevel(*modification));
+ }
SubmitBulkJob(modification, false /* not an anonymization */, call, body);
}
=====================================
OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp
=====================================
@@ -211,6 +211,15 @@ namespace Orthanc
{
LOG(ERROR) << "Cannot import non-DICOM file from ZIP archive: " << filename;
}
+ else if (e.GetErrorCode() == ErrorCode_InexistentTag)
+ {
+ /**
+ * Allow upload of ZIP archives containing a DICOMDIR
+ * file (new in Orthanc 1.9.7):
+ * https://groups.google.com/g/orthanc-users/c/sgBU89o4nhU/m/kbRAYiQUAAAJ
+ **/
+ LOG(ERROR) << "Ignoring what is probably a DICOMDIR file within a ZIP archive: \"" << filename << "\"";
+ }
else
{
throw;
=====================================
OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp
=====================================
@@ -663,9 +663,11 @@ namespace Orthanc
{
}
+ // "dicom" is non-NULL iff. "RequiresDicomTags() == true"
virtual void Handle(RestApiGetCall& call,
std::unique_ptr<ImageAccessor>& decoded,
- const DicomMap& dicom) = 0;
+ const ParsedDicomFile* dicom,
+ unsigned int frame) = 0;
virtual bool RequiresDicomTags() const = 0;
@@ -776,7 +778,6 @@ namespace Orthanc
return;
}
- DicomMap dicom;
std::unique_ptr<ImageAccessor> decoded;
try
@@ -799,7 +800,11 @@ namespace Orthanc
* interpretation, and with windowing parameters.
**/
ServerContext::DicomCacheLocker locker(context, publicId);
- OrthancConfiguration::DefaultExtractDicomSummary(dicom, locker.GetDicom());
+ handler.Handle(call, decoded, &locker.GetDicom(), frame);
+ }
+ else
+ {
+ handler.Handle(call, decoded, NULL, frame);
}
}
catch (OrthancException& e)
@@ -823,7 +828,6 @@ namespace Orthanc
return;
}
- handler.Handle(call, decoded, dicom);
}
@@ -865,13 +869,22 @@ namespace Orthanc
virtual void Handle(RestApiGetCall& call,
std::unique_ptr<ImageAccessor>& decoded,
- const DicomMap& dicom) ORTHANC_OVERRIDE
+ const ParsedDicomFile* dicom,
+ unsigned int frame) ORTHANC_OVERRIDE
{
bool invert = false;
if (mode_ == ImageExtractionMode_Preview)
{
- DicomImageInformation info(dicom);
+ if (dicom == NULL)
+ {
+ throw OrthancException(ErrorCode_InternalError);
+ }
+
+ DicomMap tags;
+ OrthancConfiguration::DefaultExtractDicomSummary(tags, *dicom);
+
+ DicomImageInformation info(tags);
invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1);
}
@@ -888,40 +901,8 @@ namespace Orthanc
class RenderedFrameHandler : public IDecodedFrameHandler
{
private:
- static void GetDicomParameters(bool& invert,
- float& rescaleSlope,
- float& rescaleIntercept,
- float& windowWidth,
- float& windowCenter,
- const DicomMap& dicom)
- {
- DicomImageInformation info(dicom);
-
- invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1);
-
- rescaleSlope = 1.0f;
- rescaleIntercept = 0.0f;
-
- if (dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_SLOPE) &&
- dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_INTERCEPT))
- {
- dicom.ParseFloat(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE);
- dicom.ParseFloat(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT);
- }
-
- windowWidth = static_cast<float>(1 << info.GetBitsStored()) * rescaleSlope;
- windowCenter = windowWidth / 2.0f + rescaleIntercept;
-
- if (dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_CENTER) &&
- dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_WIDTH))
- {
- dicom.ParseFirstFloat(windowCenter, Orthanc::DICOM_TAG_WINDOW_CENTER);
- dicom.ParseFirstFloat(windowWidth, Orthanc::DICOM_TAG_WINDOW_WIDTH);
- }
- }
-
- static void GetUserArguments(float& windowWidth /* inout */,
- float& windowCenter /* inout */,
+ static void GetUserArguments(double& windowWidth /* inout */,
+ double& windowCenter /* inout */,
unsigned int& argWidth,
unsigned int& argHeight,
bool& smooth,
@@ -933,30 +914,18 @@ namespace Orthanc
static const char* ARG_HEIGHT = "height";
static const char* ARG_SMOOTH = "smooth";
- if (call.HasArgument(ARG_WINDOW_WIDTH))
+ if (call.HasArgument(ARG_WINDOW_WIDTH) &&
+ !SerializationToolbox::ParseDouble(windowWidth, call.GetArgument(ARG_WINDOW_WIDTH, "")))
{
- try
- {
- windowWidth = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_WIDTH, ""));
- }
- catch (boost::bad_lexical_cast&)
- {
- throw OrthancException(ErrorCode_ParameterOutOfRange,
- "Bad value for argument: " + std::string(ARG_WINDOW_WIDTH));
- }
+ throw OrthancException(ErrorCode_ParameterOutOfRange,
+ "Bad value for argument: " + std::string(ARG_WINDOW_WIDTH));
}
- if (call.HasArgument(ARG_WINDOW_CENTER))
+ if (call.HasArgument(ARG_WINDOW_CENTER) &&
+ !SerializationToolbox::ParseDouble(windowCenter, call.GetArgument(ARG_WINDOW_CENTER, "")))
{
- try
- {
- windowCenter = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_CENTER, ""));
- }
- catch (boost::bad_lexical_cast&)
- {
- throw OrthancException(ErrorCode_ParameterOutOfRange,
- "Bad value for argument: " + std::string(ARG_WINDOW_CENTER));
- }
+ throw OrthancException(ErrorCode_ParameterOutOfRange,
+ "Bad value for argument: " + std::string(ARG_WINDOW_CENTER));
}
argWidth = 0;
@@ -1018,12 +987,22 @@ namespace Orthanc
public:
virtual void Handle(RestApiGetCall& call,
std::unique_ptr<ImageAccessor>& decoded,
- const DicomMap& dicom) ORTHANC_OVERRIDE
+ const ParsedDicomFile* dicom,
+ unsigned int frame) ORTHANC_OVERRIDE
{
- bool invert;
- float rescaleSlope, rescaleIntercept, windowWidth, windowCenter;
- GetDicomParameters(invert, rescaleSlope, rescaleIntercept, windowWidth, windowCenter, dicom);
-
+ if (dicom == NULL)
+ {
+ throw OrthancException(ErrorCode_InternalError);
+ }
+
+ PhotometricInterpretation photometric;
+ const bool invert = (dicom->LookupPhotometricInterpretation(photometric) &&
+ photometric == PhotometricInterpretation_Monochrome1);
+
+ double rescaleIntercept, rescaleSlope, windowCenter, windowWidth;
+ dicom->GetRescale(rescaleIntercept, rescaleSlope, frame);
+ dicom->GetDefaultWindowing(windowCenter, windowWidth, frame);
+
unsigned int argWidth, argHeight;
bool smooth;
GetUserArguments(windowWidth, windowCenter, argWidth, argHeight, smooth, call);
@@ -1093,16 +1072,16 @@ namespace Orthanc
windowWidth = 1;
}
- if (std::abs(rescaleSlope) <= 0.1f)
+ if (std::abs(rescaleSlope) <= 0.1)
{
- rescaleSlope = 0.1f;
+ rescaleSlope = 0.1;
}
- const float scaling = 255.0f * rescaleSlope / windowWidth;
- const float offset = (rescaleIntercept - windowCenter + windowWidth / 2.0f) / rescaleSlope;
+ const double scaling = 255.0 * rescaleSlope / windowWidth;
+ const double offset = (rescaleIntercept - windowCenter + windowWidth / 2.0) / rescaleSlope;
std::unique_ptr<ImageAccessor> rescaled(new Image(PixelFormat_Grayscale8, decoded->GetWidth(), decoded->GetHeight(), false));
- ImageProcessing::ShiftScale(*rescaled, converted, offset, scaling, false);
+ ImageProcessing::ShiftScale(*rescaled, converted, static_cast<float>(offset), static_cast<float>(scaling), false);
if (targetWidth == decoded->GetWidth() &&
targetHeight == decoded->GetHeight())
=====================================
OrthancServer/Sources/main.cpp
=====================================
@@ -279,6 +279,7 @@ private:
bool alwaysAllowEcho_;
bool alwaysAllowFind_; // New in Orthanc 1.9.0
bool alwaysAllowGet_; // New in Orthanc 1.9.0
+ bool alwaysAllowMove_; // New in Orthanc 1.9.7
bool alwaysAllowStore_;
public:
@@ -290,6 +291,7 @@ public:
alwaysAllowEcho_ = lock.GetConfiguration().GetBooleanParameter("DicomAlwaysAllowEcho", true);
alwaysAllowFind_ = lock.GetConfiguration().GetBooleanParameter("DicomAlwaysAllowFind", false);
alwaysAllowGet_ = lock.GetConfiguration().GetBooleanParameter("DicomAlwaysAllowGet", false);
+ alwaysAllowMove_ = lock.GetConfiguration().GetBooleanParameter("DicomAlwaysAllowMove", false);
alwaysAllowStore_ = lock.GetConfiguration().GetBooleanParameter("DicomAlwaysAllowStore", true);
}
@@ -302,6 +304,11 @@ public:
{
LOG(WARNING) << "Security risk in DICOM SCP: C-GET requests are always allowed, even from unknown modalities";
}
+
+ if (alwaysAllowMove_)
+ {
+ LOG(WARNING) << "Security risk in DICOM SCP: C-MOOVE requests are always allowed, even from unknown modalities";
+ }
}
virtual bool IsAllowedConnection(const std::string& remoteIp,
@@ -314,6 +321,7 @@ public:
if (alwaysAllowEcho_ ||
alwaysAllowFind_ ||
alwaysAllowGet_ ||
+ alwaysAllowMove_ ||
alwaysAllowStore_)
{
return true;
@@ -368,6 +376,12 @@ public:
// Incoming C-Get requests are always accepted, even from unknown AET
return true;
}
+ else if (type == DicomRequestType_Move &&
+ alwaysAllowMove_)
+ {
+ // Incoming C-Move requests are always accepted, even from unknown AET
+ return true;
+ }
else
{
bool checkIp;
View it on GitLab: https://salsa.debian.org/med-team/orthanc/-/commit/e757fddc385828c04b879d2e06ccebf051526361
--
View it on GitLab: https://salsa.debian.org/med-team/orthanc/-/commit/e757fddc385828c04b879d2e06ccebf051526361
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/20210831/5245b563/attachment-0001.htm>
More information about the debian-med-commit
mailing list