# HG changeset patch # User Sebastien Jodogne # Date 1444745435 -7200 # Node ID b80e76dd1d56fa811ccf53dd557f68f8ff51f32a # Parent 9980875edc7cabc2cedd6841d79df328d31c6489 ordered-slices continued diff -r 9980875edc7c -r b80e76dd1d56 Core/Enumerations.cpp --- a/Core/Enumerations.cpp Mon Oct 12 17:49:23 2015 +0200 +++ b/Core/Enumerations.cpp Tue Oct 13 16:10:35 2015 +0200 @@ -44,11 +44,6 @@ // "Resources/GenerateErrorCodes.py" const char* EnumerationToString(ErrorCode error) { - if (error >= ErrorCode_START_PLUGINS) - { - return "Error encountered within some plugin"; - } - switch (error) { case ErrorCode_InternalError: @@ -318,8 +313,21 @@ case ErrorCode_DatabaseNotInitialized: return "Plugin trying to call the database during its initialization"; + case ErrorCode_SslDisabled: + return "Orthanc has been built without SSL support"; + + case ErrorCode_CannotOrderSlices: + return "Unable to order the slices of the series"; + default: - return "Unknown error code"; + if (error >= ErrorCode_START_PLUGINS) + { + return "Error encountered within some plugin"; + } + else + { + return "Unknown error code"; + } } } diff -r 9980875edc7c -r b80e76dd1d56 Core/Enumerations.h --- a/Core/Enumerations.h Mon Oct 12 17:49:23 2015 +0200 +++ b/Core/Enumerations.h Tue Oct 13 16:10:35 2015 +0200 @@ -135,6 +135,7 @@ ErrorCode_DatabaseBackendAlreadyRegistered = 2037 /*!< Another plugin has already registered a custom database back-end */, ErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */, ErrorCode_SslDisabled = 2039 /*!< Orthanc has been built without SSL support */, + ErrorCode_CannotOrderSlices = 2040 /*!< Unable to order the slices of the series */, ErrorCode_START_PLUGINS = 1000000 }; diff -r 9980875edc7c -r b80e76dd1d56 NEWS --- a/NEWS Mon Oct 12 17:49:23 2015 +0200 +++ b/NEWS Tue Oct 13 16:10:35 2015 +0200 @@ -5,7 +5,7 @@ * "/tools/create-dicom": Support of binary tags encoded using data URI scheme * "/tools/create-dicom": Support of hierarchical structures (creation of sequences) * "/modify" can insert/modify sequences -* "/series/.../ordered-slices" to order the slices of a 2D+t, 3D or 3D+t image +* "/series/.../ordered-slices" to order the slices of a 2D+t or 3D image * New URIs for attachments: ".../compress", ".../uncompress" and ".../is-compressed" Plugins diff -r 9980875edc7c -r b80e76dd1d56 OrthancServer/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Mon Oct 12 17:49:23 2015 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Tue Oct 13 16:10:35 2015 +0200 @@ -1101,6 +1101,9 @@ ServerIndex& index = OrthancRestApi::GetIndex(call); SliceOrdering ordering(index, id); + Json::Value result; + ordering.Format(result); + call.GetOutput().AnswerJson(result); } diff -r 9980875edc7c -r b80e76dd1d56 OrthancServer/SliceOrdering.cpp --- a/OrthancServer/SliceOrdering.cpp Mon Oct 12 17:49:23 2015 +0200 +++ b/OrthancServer/SliceOrdering.cpp Tue Oct 13 16:10:35 2015 +0200 @@ -33,9 +33,13 @@ #include "PrecompiledHeadersServer.h" #include "SliceOrdering.h" +#include "../Core/Logging.h" #include "../Core/Toolbox.h" +#include "ServerEnumerations.h" +#include #include +#include namespace Orthanc @@ -89,23 +93,40 @@ } - struct SliceOrdering::Instance + struct SliceOrdering::Instance : public boost::noncopyable { + private: std::string instanceId_; bool hasPosition_; Vector position_; bool hasIndexInSeries_; size_t indexInSeries_; + unsigned int framesCount_; + public: Instance(ServerIndex& index, - const std::string& instanceId) : - instanceId_(instanceId) + const std::string& instanceId) : + instanceId_(instanceId), + framesCount_(1) { DicomMap instance; if (!index.GetMainDicomTags(instance, instanceId, ResourceType_Instance, ResourceType_Instance)) { throw OrthancException(ErrorCode_UnknownResource); } + + const DicomValue* frames = instance.TestAndGetValue(DICOM_TAG_NUMBER_OF_FRAMES); + if (frames != NULL && + !frames->IsNull()) + { + try + { + framesCount_ = boost::lexical_cast(frames->AsString()); + } + catch (boost::bad_lexical_cast&) + { + } + } std::vector tmp; hasPosition_ = TokenizeVector(tmp, instance, DICOM_TAG_IMAGE_POSITION_PATIENT, 3); @@ -132,9 +153,68 @@ { } } + + const std::string& GetIdentifier() const + { + return instanceId_; + } + + bool HasPosition() const + { + return hasPosition_; + } + + float ComputeRelativePosition(const Vector& normal) const + { + assert(HasPosition()); + return (normal[0] * position_[0] + + normal[1] * position_[1] + + normal[2] * position_[2]); + } + + bool HasIndexInSeries() const + { + return hasIndexInSeries_; + } + + size_t GetIndexInSeries() const + { + assert(HasIndexInSeries()); + return indexInSeries_; + } + + unsigned int GetFramesCount() const + { + return framesCount_; + } }; + class SliceOrdering::PositionComparator + { + private: + const Vector& normal_; + + public: + PositionComparator(const Vector& normal) : normal_(normal) + { + } + + int operator() (const Instance* a, + const Instance* b) const + { + return a->ComputeRelativePosition(normal_) < b->ComputeRelativePosition(normal_); + } + }; + + + bool SliceOrdering::IndexInSeriesComparator(const SliceOrdering::Instance* a, + const SliceOrdering::Instance* b) + { + return a->GetIndexInSeries() < b->GetIndexInSeries(); + } + + void SliceOrdering::ComputeNormal() { DicomMap series; @@ -171,6 +251,12 @@ bool SliceOrdering::SortUsingPositions() { + if (instances_.size() <= 1) + { + // One single instance: It is sorted by default + return true; + } + if (!hasNormal_) { return false; @@ -179,13 +265,60 @@ for (size_t i = 0; i < instances_.size(); i++) { assert(instances_[i] != NULL); - if (!instances_[i]->hasPosition_) + if (!instances_[i]->HasPosition()) { return false; } } + PositionComparator comparator(normal_); + std::sort(instances_.begin(), instances_.end(), comparator); + + float a = instances_.front()->ComputeRelativePosition(normal_); + float b = instances_.back()->ComputeRelativePosition(normal_); + + if (std::fabs(b - a) <= 10.0f * std::numeric_limits::epsilon()) + { + // Not enough difference between the minimum and maximum + // positions along the normal of the volume + return false; + } + else + { + // This is a 3D volume + isVolume_ = true; + return true; + } + } + + + bool SliceOrdering::SortUsingIndexInSeries() + { + if (instances_.size() <= 1) + { + // One single instance: It is sorted by default + return true; + } + + for (size_t i = 0; i < instances_.size(); i++) + { + assert(instances_[i] != NULL); + if (!instances_[i]->HasIndexInSeries()) + { + return false; + } + } + + std::sort(instances_.begin(), instances_.end(), IndexInSeriesComparator); + for (size_t i = 1; i < instances_.size(); i++) + { + if (instances_[i - 1]->GetIndexInSeries() == instances_[i]->GetIndexInSeries()) + { + // The current "IndexInSeries" occurs 2 times: Not a proper ordering + return false; + } + } return true; } @@ -194,10 +327,18 @@ SliceOrdering::SliceOrdering(ServerIndex& index, const std::string& seriesId) : index_(index), - seriesId_(seriesId) + seriesId_(seriesId), + isVolume_(false) { ComputeNormal(); CreateInstances(); + + if (!SortUsingPositions() && + !SortUsingIndexInSeries()) + { + LOG(ERROR) << "Unable to order the slices of the series " << seriesId; + throw OrthancException(ErrorCode_CannotOrderSlices); + } } @@ -212,4 +353,57 @@ } } } + + + const std::string& SliceOrdering::GetInstanceId(size_t index) const + { + if (index >= instances_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return instances_[index]->GetIdentifier(); + } + } + + + unsigned int SliceOrdering::GetFramesCount(size_t index) const + { + if (index >= instances_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return instances_[index]->GetFramesCount(); + } + } + + + void SliceOrdering::Format(Json::Value& result) const + { + result = Json::objectValue; + result["Type"] = (isVolume_ ? "Volume" : "Sequence"); + + Json::Value tmp = Json::arrayValue; + for (size_t i = 0; i < GetInstancesCount(); i++) + { + tmp.append(GetBasePath(ResourceType_Instance, GetInstanceId(i)) + "/file"); + } + + result["Dicom"] = tmp; + + tmp.clear(); + for (size_t i = 0; i < GetInstancesCount(); i++) + { + std::string base = GetBasePath(ResourceType_Instance, GetInstanceId(i)); + for (size_t j = 0; j < GetFramesCount(i); j++) + { + tmp.append(base + "/frames/" + boost::lexical_cast(j)); + } + } + + result["Slices"] = tmp; + } } diff -r 9980875edc7c -r b80e76dd1d56 OrthancServer/SliceOrdering.h --- a/OrthancServer/SliceOrdering.h Mon Oct 12 17:49:23 2015 +0200 +++ b/OrthancServer/SliceOrdering.h Tue Oct 13 16:10:35 2015 +0200 @@ -42,12 +42,17 @@ typedef float Vector[3]; struct Instance; + struct PositionComparator; ServerIndex& index_; std::string seriesId_; bool hasNormal_; Vector normal_; std::vector instances_; + bool isVolume_; + + static bool IndexInSeriesComparator(const SliceOrdering::Instance* a, + const SliceOrdering::Instance* b); void ComputeNormal(); @@ -55,10 +60,23 @@ bool SortUsingPositions(); + bool SortUsingIndexInSeries(); + public: SliceOrdering(ServerIndex& index, const std::string& seriesId); ~SliceOrdering(); + + size_t GetInstancesCount() const + { + return instances_.size(); + } + + const std::string& GetInstanceId(size_t index) const; + + unsigned int GetFramesCount(size_t index) const; + + void Format(Json::Value& result) const; }; } diff -r 9980875edc7c -r b80e76dd1d56 Plugins/Include/orthanc/OrthancCPlugin.h --- a/Plugins/Include/orthanc/OrthancCPlugin.h Mon Oct 12 17:49:23 2015 +0200 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Tue Oct 13 16:10:35 2015 +0200 @@ -268,6 +268,7 @@ OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037 /*!< Another plugin has already registered a custom database back-end */, OrthancPluginErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */, OrthancPluginErrorCode_SslDisabled = 2039 /*!< Orthanc has been built without SSL support */, + OrthancPluginErrorCode_CannotOrderSlices = 2040 /*!< Unable to order the slices of the series */, _OrthancPluginErrorCode_INTERNAL = 0x7fffffff } OrthancPluginErrorCode; diff -r 9980875edc7c -r b80e76dd1d56 Resources/ErrorCodes.json --- a/Resources/ErrorCodes.json Mon Oct 12 17:49:23 2015 +0200 +++ b/Resources/ErrorCodes.json Tue Oct 13 16:10:35 2015 +0200 @@ -497,5 +497,10 @@ "Code": 2039, "Name": "SslDisabled", "Description": "Orthanc has been built without SSL support" + }, + { + "Code": 2040, + "Name": "CannotOrderSlices", + "Description": "Unable to order the slices of the series" } ]