Skip to content
Commits on Source (5)
repo: 3959d33612ccaadc0d4d707227fbed09ac35e5fe
node: afb581263e1d79e67ebf2fc1ca85780f9f3783e5
branch: Orthanc-1.4.2
node: 38b71a1006fd22750bb8f9ada9d2c6085bfe26de
branch: Orthanc-1.5.0
latesttag: dcmtk-3.6.1
latesttagdistance: 423
changessincelatesttag: 465
latesttagdistance: 554
changessincelatesttag: 613
......@@ -14,7 +14,8 @@ Authors of Orthanc
4000 Liege
Belgium
* Osimis S.A. <info@osimis.io>
* Osimis S.A.
Rue du Bois Saint-Jean 15/1
4102 Seraing
Belgium
http://www.osimis.io/
......@@ -58,6 +58,7 @@ set(ORTHANC_SERVER_SOURCES
OrthancServer/DicomInstanceToStore.cpp
OrthancServer/ExportedResource.cpp
OrthancServer/LuaScripting.cpp
OrthancServer/OrthancConfiguration.cpp
OrthancServer/OrthancFindRequestHandler.cpp
OrthancServer/OrthancHttpHandler.cpp
OrthancServer/OrthancInitialization.cpp
......@@ -70,6 +71,8 @@ set(ORTHANC_SERVER_SOURCES
OrthancServer/OrthancRestApi/OrthancRestResources.cpp
OrthancServer/OrthancRestApi/OrthancRestSystem.cpp
OrthancServer/QueryRetrieveHandler.cpp
OrthancServer/Search/DatabaseLookup.cpp
OrthancServer/Search/DicomTagConstraint.cpp
OrthancServer/Search/HierarchicalMatcher.cpp
OrthancServer/Search/IFindConstraint.cpp
OrthancServer/Search/ListConstraint.cpp
......@@ -84,7 +87,9 @@ set(ORTHANC_SERVER_SOURCES
OrthancServer/ServerIndex.cpp
OrthancServer/ServerJobs/ArchiveJob.cpp
OrthancServer/ServerJobs/DicomModalityStoreJob.cpp
OrthancServer/ServerJobs/DicomMoveScuJob.cpp
OrthancServer/ServerJobs/LuaJobManager.cpp
OrthancServer/ServerJobs/MergeStudyJob.cpp
OrthancServer/ServerJobs/Operations/DeleteResourceOperation.cpp
OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp
OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp
......@@ -93,29 +98,31 @@ set(ORTHANC_SERVER_SOURCES
OrthancServer/ServerJobs/OrthancJobUnserializer.cpp
OrthancServer/ServerJobs/OrthancPeerStoreJob.cpp
OrthancServer/ServerJobs/ResourceModificationJob.cpp
OrthancServer/ServerJobs/SplitStudyJob.cpp
OrthancServer/ServerToolbox.cpp
OrthancServer/SliceOrdering.cpp
)
set(ORTHANC_UNIT_TESTS_SOURCES
UnitTestsSources/DatabaseLookupTests.cpp
UnitTestsSources/DicomMapTests.cpp
UnitTestsSources/FileStorageTests.cpp
UnitTestsSources/FromDcmtkTests.cpp
UnitTestsSources/MemoryCacheTests.cpp
UnitTestsSources/ImageProcessingTests.cpp
UnitTestsSources/ImageTests.cpp
UnitTestsSources/JpegLosslessTests.cpp
UnitTestsSources/LuaTests.cpp
UnitTestsSources/MemoryCacheTests.cpp
UnitTestsSources/MultiThreadingTests.cpp
UnitTestsSources/RestApiTests.cpp
UnitTestsSources/SQLiteTests.cpp
UnitTestsSources/SQLiteChromiumTests.cpp
UnitTestsSources/SQLiteTests.cpp
UnitTestsSources/ServerIndexTests.cpp
UnitTestsSources/StreamTests.cpp
UnitTestsSources/UnitTestsMain.cpp
UnitTestsSources/VersionsTests.cpp
UnitTestsSources/ZipTests.cpp
UnitTestsSources/LuaTests.cpp
UnitTestsSources/MultiThreadingTests.cpp
UnitTestsSources/UnitTestsMain.cpp
UnitTestsSources/ImageProcessingTests.cpp
UnitTestsSources/JpegLosslessTests.cpp
UnitTestsSources/StreamTests.cpp
)
......
......@@ -47,6 +47,8 @@ namespace Orthanc
{
delete it->second;
archive_.erase(it);
lru_.Invalidate(id);
}
}
......@@ -59,7 +61,7 @@ namespace Orthanc
if (it == that.archive_.end())
{
throw OrthancException(ErrorCode_InexistentItem);
item_ = NULL;
}
else
{
......@@ -69,6 +71,20 @@ namespace Orthanc
}
IDynamicObject& SharedArchive::Accessor::GetItem() const
{
if (item_ == NULL)
{
// "IsValid()" should have been called
throw OrthancException(ErrorCode_BadSequenceOfCalls);
}
else
{
return *item_;
}
}
SharedArchive::SharedArchive(size_t maxSize) :
maxSize_(maxSize)
{
......@@ -96,12 +112,12 @@ namespace Orthanc
if (archive_.size() == maxSize_)
{
// The quota has been reached, remove the oldest element
std::string oldest = lru_.RemoveOldest();
RemoveInternal(oldest);
RemoveInternal(lru_.GetOldest());
}
std::string id = Toolbox::GenerateUuid();
RemoveInternal(id); // Should never be useful because of UUID
archive_[id] = obj;
lru_.Add(id);
......@@ -113,7 +129,6 @@ namespace Orthanc
{
boost::mutex::scoped_lock lock(mutex_);
RemoveInternal(id);
lru_.Invalidate(id);
}
......@@ -121,14 +136,14 @@ namespace Orthanc
{
items.clear();
boost::mutex::scoped_lock lock(mutex_);
for (Archive::const_iterator it = archive_.begin();
it != archive_.end(); ++it)
{
items.push_back(it->first);
boost::mutex::scoped_lock lock(mutex_);
for (Archive::const_iterator it = archive_.begin();
it != archive_.end(); ++it)
{
items.push_back(it->first);
}
}
}
}
......@@ -72,10 +72,12 @@ namespace Orthanc
Accessor(SharedArchive& that,
const std::string& id);
IDynamicObject& GetItem() const
bool IsValid() const
{
return *item_;
}
return item_ != NULL;
}
IDynamicObject& GetItem() const;
};
......
......@@ -45,8 +45,8 @@ namespace Orthanc
{
if (level >= 10)
{
LOG(ERROR) << "Zlib compression level must be between 0 (no compression) and 9 (highest compression)";
throw OrthancException(ErrorCode_ParameterOutOfRange);
throw OrthancException(ErrorCode_ParameterOutOfRange,
"Zlib compression level must be between 0 (no compression) and 9 (highest compression)");
}
compressionLevel_ = level;
......@@ -63,8 +63,7 @@ namespace Orthanc
if (compressedSize < sizeof(uint64_t))
{
LOG(ERROR) << "The compressed buffer is ill-formed";
throw OrthancException(ErrorCode_CorruptedFile);
throw OrthancException(ErrorCode_CorruptedFile, "The compressed buffer is ill-formed");
}
uint64_t size;
......
......@@ -271,8 +271,8 @@ namespace Orthanc
// The uncompressed size was not that properly guess, presumably
// because of a file size over 4GB. Should fallback to
// stream-based decompression.
LOG(ERROR) << "The uncompressed size of a gzip-encoded buffer was not properly guessed";
throw OrthancException(ErrorCode_NotImplemented);
throw OrthancException(ErrorCode_NotImplemented,
"The uncompressed size of a gzip-encoded buffer was not properly guessed");
}
}
}
......@@ -124,8 +124,8 @@ namespace Orthanc
if (path_.size() == 0)
{
LOG(ERROR) << "Please call SetOutputPath() before creating the file";
throw OrthancException(ErrorCode_BadSequenceOfCalls);
throw OrthancException(ErrorCode_BadSequenceOfCalls,
"Please call SetOutputPath() before creating the file");
}
hasFileInZip_ = false;
......@@ -168,8 +168,8 @@ namespace Orthanc
{
if (level >= 10)
{
LOG(ERROR) << "ZIP compression level must be between 0 (no compression) and 9 (highest compression)";
throw OrthancException(ErrorCode_ParameterOutOfRange);
throw OrthancException(ErrorCode_ParameterOutOfRange,
"ZIP compression level must be between 0 (no compression) and 9 (highest compression)");
}
Close();
......@@ -228,8 +228,7 @@ namespace Orthanc
{
if (!hasFileInZip_)
{
LOG(ERROR) << "Call first OpenFile()";
throw OrthancException(ErrorCode_BadSequenceOfCalls);
throw OrthancException(ErrorCode_BadSequenceOfCalls, "Call first OpenFile()");
}
const size_t maxBytesInAStep = std::numeric_limits<int32_t>::max();
......
......@@ -117,8 +117,8 @@ namespace Orthanc
if (!HasPrefixWithUncompressedSize())
{
LOG(ERROR) << "Cannot guess the uncompressed size of a zlib-encoded buffer";
throw OrthancException(ErrorCode_InternalError);
throw OrthancException(ErrorCode_InternalError,
"Cannot guess the uncompressed size of a zlib-encoded buffer");
}
uint64_t uncompressedSize = ReadUncompressedSizePrefix(compressed, compressedSize);
......
......@@ -45,6 +45,7 @@
#include <limits>
#include <cassert>
#include <stdio.h>
#include <memory>
namespace Orthanc
{
......@@ -213,6 +214,23 @@ namespace Orthanc
isSigned_ = (pixelRepresentation != 0 ? true : false);
}
DicomImageInformation* DicomImageInformation::Clone() const
{
std::auto_ptr<DicomImageInformation> target(new DicomImageInformation);
target->width_ = width_;
target->height_ = height_;
target->samplesPerPixel_ = samplesPerPixel_;
target->numberOfFrames_ = numberOfFrames_;
target->isPlanar_ = isPlanar_;
target->isSigned_ = isSigned_;
target->bytesPerValue_ = bytesPerValue_;
target->bitsAllocated_ = bitsAllocated_;
target->bitsStored_ = bitsStored_;
target->highBit_ = highBit_;
target->photometric_ = photometric_;
return target.release();
}
bool DicomImageInformation::ExtractPixelFormat(PixelFormat& format,
bool ignorePhotometricInterpretation) const
......@@ -253,6 +271,12 @@ namespace Orthanc
format = PixelFormat_SignedGrayscale16;
return true;
}
if (GetBitsAllocated() == 32 && GetChannelCount() == 1 && !IsSigned())
{
format = PixelFormat_Grayscale32;
return true;
}
}
if (GetBitsStored() == 8 &&
......
......@@ -57,9 +57,16 @@ namespace Orthanc
PhotometricInterpretation photometric_;
protected:
explicit DicomImageInformation()
{
}
public:
explicit DicomImageInformation(const DicomMap& values);
DicomImageInformation* Clone() const;
unsigned int GetWidth() const
{
return width_;
......
......@@ -53,7 +53,7 @@ namespace Orthanc
seriesUid_.size() == 0 ||
instanceUid_.size() == 0)
{
throw OrthancException(ErrorCode_BadFileFormat);
throw OrthancException(ErrorCode_BadFileFormat, "missing StudyInstanceUID, SeriesInstanceUID or SOPInstanceUID");
}
}
......
......@@ -179,12 +179,11 @@ namespace Orthanc
}
void DicomMap::Clear()
{
for (Map::iterator it = map_.begin(); it != map_.end(); ++it)
{
assert(it->second != NULL);
delete it->second;
}
......@@ -981,4 +980,51 @@ namespace Orthanc
return value->ParseDouble(result);
}
}
void DicomMap::Serialize(Json::Value& target) const
{
target = Json::objectValue;
for (Map::const_iterator it = map_.begin(); it != map_.end(); ++it)
{
assert(it->second != NULL);
std::string tag = it->first.Format();
Json::Value value;
it->second->Serialize(value);
target[tag] = value;
}
}
void DicomMap::Unserialize(const Json::Value& source)
{
Clear();
if (source.type() != Json::objectValue)
{
throw OrthancException(ErrorCode_BadFileFormat);
}
Json::Value::Members tags = source.getMemberNames();
for (size_t i = 0; i < tags.size(); i++)
{
DicomTag tag(0, 0);
if (!DicomTag::ParseHexadecimal(tag, tags[i].c_str()) ||
map_.find(tag) != map_.end())
{
throw OrthancException(ErrorCode_BadFileFormat);
}
std::auto_ptr<DicomValue> value(new DicomValue);
value->Unserialize(source[tags[i]]);
map_[tag] = value.release();
}
}
}
......@@ -89,6 +89,17 @@ namespace Orthanc
void Clear();
void SetNullValue(uint16_t group,
uint16_t element)
{
SetValue(group, element, new DicomValue);
}
void SetNullValue(const DicomTag& tag)
{
SetValue(tag, new DicomValue);
}
void SetValue(uint16_t group,
uint16_t element,
const DicomValue& value)
......@@ -198,12 +209,16 @@ namespace Orthanc
const DicomTag& tag) const;
bool ParseUnsignedInteger64(uint64_t& result,
const DicomTag& tag) const;
const DicomTag& tag) const;
bool ParseFloat(float& result,
const DicomTag& tag) const;
const DicomTag& tag) const;
bool ParseDouble(double& result,
const DicomTag& tag) const;
const DicomTag& tag) const;
void Serialize(Json::Value& target) const;
void Unserialize(const Json::Value& source);
};
}
......@@ -175,6 +175,17 @@ namespace Orthanc
static const DicomTag DICOM_TAG_IMAGE_COMMENTS(0x0020, 0x4000);
static const DicomTag DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION(0x0018, 0x1400);
static const DicomTag DICOM_TAG_CONTRAST_BOLUS_AGENT(0x0018, 0x0010);
static const DicomTag DICOM_TAG_STUDY_ID(0x0020, 0x0010);
static const DicomTag DICOM_TAG_SERIES_NUMBER(0x0020, 0x0011);
static const DicomTag DICOM_TAG_PATIENT_SEX(0x0010, 0x0040);
static const DicomTag DICOM_TAG_LATERALITY(0x0020, 0x0060);
static const DicomTag DICOM_TAG_BODY_PART_EXAMINED(0x0018, 0x0015);
static const DicomTag DICOM_TAG_VIEW_POSITION(0x0018, 0x5101);
static const DicomTag DICOM_TAG_MANUFACTURER(0x0008, 0x0070);
static const DicomTag DICOM_TAG_PATIENT_ORIENTATION(0x0020, 0x0020);
static const DicomTag DICOM_TAG_PATIENT_COMMENTS(0x0010, 0x4000);
static const DicomTag DICOM_TAG_PATIENT_SPECIES_DESCRIPTION(0x0010, 0x2201);
static const DicomTag DICOM_TAG_STUDY_COMMENTS(0x0032, 0x4000);
// Tags used within the Stone of Orthanc
static const DicomTag DICOM_TAG_FRAME_INCREMENT_POINTER(0x0028, 0x0009);
......
......@@ -35,6 +35,7 @@
#include "DicomValue.h"
#include "../OrthancException.h"
#include "../SerializationToolbox.h"
#include "../Toolbox.h"
#include <boost/lexical_cast.hpp>
......@@ -193,4 +194,65 @@ namespace Orthanc
return true;
}
}
static const char* KEY_TYPE = "Type";
static const char* KEY_CONTENT = "Content";
void DicomValue::Serialize(Json::Value& target) const
{
target = Json::objectValue;
switch (type_)
{
case Type_Null:
target[KEY_TYPE] = "Null";
break;
case Type_String:
target[KEY_TYPE] = "String";
target[KEY_CONTENT] = content_;
break;
case Type_Binary:
{
target[KEY_TYPE] = "Binary";
std::string base64;
Toolbox::EncodeBase64(base64, content_);
target[KEY_CONTENT] = base64;
break;
}
default:
throw OrthancException(ErrorCode_InternalError);
}
}
void DicomValue::Unserialize(const Json::Value& source)
{
std::string type = SerializationToolbox::ReadString(source, KEY_TYPE);
if (type == "Null")
{
type_ = Type_Null;
content_.clear();
}
else if (type == "String")
{
type_ = Type_String;
content_ = SerializationToolbox::ReadString(source, KEY_CONTENT);
}
else if (type == "Binary")
{
type_ = Type_Binary;
const std::string base64 =SerializationToolbox::ReadString(source, KEY_CONTENT);
Toolbox::DecodeBase64(content_, base64);
}
else
{
throw OrthancException(ErrorCode_BadFileFormat);
}
}
}
......@@ -33,9 +33,11 @@
#pragma once
#include "../Enumerations.h"
#include <stdint.h>
#include <string>
#include <boost/noncopyable.hpp>
#include <json/value.h>
#if !defined(ORTHANC_ENABLE_BASE64)
# error The macro ORTHANC_ENABLE_BASE64 must be defined
......@@ -91,7 +93,7 @@ namespace Orthanc
void FormatDataUriScheme(std::string& target) const
{
FormatDataUriScheme(target, "application/octet-stream");
FormatDataUriScheme(target, MIME_BINARY);
}
#endif
......@@ -108,6 +110,10 @@ namespace Orthanc
bool ParseFloat(float& result) const;
bool ParseDouble(double& result) const;
bool ParseDouble(double& result) const;
void Serialize(Json::Value& target) const;
void Unserialize(const Json::Value& source);
};
}
......@@ -316,8 +316,8 @@ namespace Orthanc
{
if (modalities_ == NULL)
{
LOG(ERROR) << "No list of modalities was provided to the DICOM server";
throw OrthancException(ErrorCode_BadSequenceOfCalls);
throw OrthancException(ErrorCode_BadSequenceOfCalls,
"No list of modalities was provided to the DICOM server");
}
Stop();
......@@ -327,8 +327,8 @@ namespace Orthanc
(NET_ACCEPTOR, OFstatic_cast(int, port_), /*opt_acse_timeout*/ 30, &pimpl_->network_);
if (cond.bad())
{
LOG(ERROR) << "cannot create network: " << cond.text();
throw OrthancException(ErrorCode_DicomPortInUse);
throw OrthancException(ErrorCode_DicomPortInUse,
"cannot create network: " + std::string(cond.text()));
}
continue_ = true;
......
......@@ -164,8 +164,8 @@ namespace Orthanc
{
if (cond.bad())
{
LOG(ERROR) << "DicomUserConnection: " << std::string(cond.text());
throw OrthancException(ErrorCode_NetworkProtocol);
throw OrthancException(ErrorCode_NetworkProtocol,
"DicomUserConnection: " + std::string(cond.text()));
}
}
......@@ -173,8 +173,8 @@ namespace Orthanc
{
if (!IsOpen())
{
LOG(ERROR) << "DicomUserConnection: First open the connection";
throw OrthancException(ErrorCode_NetworkProtocol);
throw OrthancException(ErrorCode_NetworkProtocol,
"DicomUserConnection: First open the connection");
}
}
......@@ -625,12 +625,14 @@ namespace Orthanc
case ResourceType_Instance:
clevel = "INSTANCE";
if (manufacturer_ == ModalityManufacturer_ClearCanvas ||
manufacturer_ == ModalityManufacturer_Dcm4Chee)
manufacturer_ == ModalityManufacturer_Dcm4Chee ||
manufacturer_ == ModalityManufacturer_GE)
{
// This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>.
// https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J
// http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "IMAGE");
clevel = "IMAGE";
}
else
{
......@@ -644,6 +646,18 @@ namespace Orthanc
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
const char* universal;
if (manufacturer_ == ModalityManufacturer_GE)
{
universal = "*";
}
else
{
universal = "";
}
// Add the expected tags for this query level.
// WARNING: Do not reorder or add "break" in this switch-case!
switch (level)
......@@ -651,27 +665,37 @@ namespace Orthanc
case ResourceType_Instance:
// SOP Instance UID
if (!fields.HasTag(0x0008, 0x0018))
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0018), "");
{
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0018), universal);
}
case ResourceType_Series:
// Series instance UID
if (!fields.HasTag(0x0020, 0x000e))
DU_putStringDOElement(dataset, DcmTagKey(0x0020, 0x000e), "");
{
DU_putStringDOElement(dataset, DcmTagKey(0x0020, 0x000e), universal);
}
case ResourceType_Study:
// Accession number
if (!fields.HasTag(0x0008, 0x0050))
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0050), "");
{
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0050), universal);
}
// Study instance UID
if (!fields.HasTag(0x0020, 0x000d))
DU_putStringDOElement(dataset, DcmTagKey(0x0020, 0x000d), "");
{
DU_putStringDOElement(dataset, DcmTagKey(0x0020, 0x000d), universal);
}
case ResourceType_Patient:
// Patient ID
if (!fields.HasTag(0x0010, 0x0020))
DU_putStringDOElement(dataset, DcmTagKey(0x0010, 0x0020), "");
{
DU_putStringDOElement(dataset, DcmTagKey(0x0010, 0x0020), universal);
}
break;
default:
......@@ -709,7 +733,8 @@ namespace Orthanc
case ResourceType_Instance:
if (manufacturer_ == ModalityManufacturer_ClearCanvas ||
manufacturer_ == ModalityManufacturer_Dcm4Chee)
manufacturer_ == ModalityManufacturer_Dcm4Chee ||
manufacturer_ == ModalityManufacturer_GE)
{
// This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>.
// https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J
......
......@@ -200,9 +200,9 @@ namespace Orthanc
assert(data.modalities_ != NULL);
if (!data.modalities_->LookupAETitle(modality, *data.remoteAet_))
{
LOG(ERROR) << "Modality with AET \"" << *data.remoteAet_
<< "\" is not defined in the \"DicomModalities\" configuration option";
throw OrthancException(ErrorCode_UnknownModality);
throw OrthancException(ErrorCode_UnknownModality,
"Modality with AET \"" + (*data.remoteAet_) +
"\" is not defined in the \"DicomModalities\" configuration option");
}
......