# HG changeset patch # User Sebastien Jodogne # Date 1355925438 -3600 # Node ID 4eea080e6e7a6f7e75ae08f36f1bb1c974fe9ce8 # Parent c76a35a85c691b078f9859f8d20b0c1dedc37899 refactoring diff -r c76a35a85c69 -r 4eea080e6e7a Core/Enumerations.h --- a/Core/Enumerations.h Tue Dec 18 19:01:01 2012 +0100 +++ b/Core/Enumerations.h Wed Dec 19 14:57:18 2012 +0100 @@ -48,6 +48,7 @@ ErrorCode_BadParameterType, ErrorCode_BadSequenceOfCalls, ErrorCode_InexistentItem, + ErrorCode_BadRequest, // Specific error codes ErrorCode_UriSyntax, diff -r c76a35a85c69 -r 4eea080e6e7a Core/OrthancException.cpp --- a/Core/OrthancException.cpp Tue Dec 18 19:01:01 2012 +0100 +++ b/Core/OrthancException.cpp Wed Dec 19 14:57:18 2012 +0100 @@ -99,6 +99,9 @@ case ErrorCode_InexistentItem: return "Accessing an inexistent item"; + case ErrorCode_BadRequest: + return "Bad request"; + case ErrorCode_Custom: default: return "???"; diff -r c76a35a85c69 -r 4eea080e6e7a Core/RestApi/RestApi.cpp --- a/Core/RestApi/RestApi.cpp Tue Dec 18 19:01:01 2012 +0100 +++ b/Core/RestApi/RestApi.cpp Wed Dec 19 14:57:18 2012 +0100 @@ -37,6 +37,29 @@ namespace Orthanc { + bool RestApi::SharedCall::ParseJsonRequestInternal(Json::Value& result, + const char* request) + { + result.clear(); + Json::Reader reader; + return reader.parse(request, result); + } + + + bool RestApi::GetCall::ParseJsonRequest(Json::Value& result) const + { + result.clear(); + + for (HttpHandler::Arguments::const_iterator + it = getArguments_->begin(); it != getArguments_->end(); it++) + { + result[it->first] = result[it->second]; + } + + return true; + } + + bool RestApi::IsGetAccepted(const UriComponents& uri) { for (GetHandlers::const_iterator it = getHandlers_.begin(); diff -r c76a35a85c69 -r 4eea080e6e7a Core/RestApi/RestApi.h --- a/Core/RestApi/RestApi.h Tue Dec 18 19:01:01 2012 +0100 +++ b/Core/RestApi/RestApi.h Wed Dec 19 14:57:18 2012 +0100 @@ -55,6 +55,10 @@ const UriComponents* trailing_; const UriComponents* fullUri_; + protected: + static bool ParseJsonRequestInternal(Json::Value& result, + const char* request); + public: RestApiOutput& GetOutput() { @@ -87,6 +91,8 @@ { return HttpHandler::GetArgument(*httpHeaders_, name, defaultValue); } + + virtual bool ParseJsonRequest(Json::Value& result) const = 0; }; @@ -109,6 +115,8 @@ { return getArguments_->find(name) != getArguments_->end(); } + + virtual bool ParseJsonRequest(Json::Value& result) const; }; class PutCall : public SharedCall @@ -119,10 +127,15 @@ const std::string* data_; public: - const std::string& GetPutBody() + const std::string& GetPutBody() const { return *data_; } + + virtual bool ParseJsonRequest(Json::Value& result) const + { + return ParseJsonRequestInternal(result, GetPutBody().c_str()); + } }; class PostCall : public SharedCall @@ -133,14 +146,25 @@ const std::string* data_; public: - const std::string& GetPostBody() + const std::string& GetPostBody() const { return *data_; } + + virtual bool ParseJsonRequest(Json::Value& result) const + { + return ParseJsonRequestInternal(result, GetPostBody().c_str()); + } }; class DeleteCall : public SharedCall { + public: + virtual bool ParseJsonRequest(Json::Value& result) const + { + result.clear(); + return true; + } }; typedef void (*GetHandler) (GetCall& call); diff -r c76a35a85c69 -r 4eea080e6e7a OrthancServer/DatabaseWrapper.cpp --- a/OrthancServer/DatabaseWrapper.cpp Tue Dec 18 19:01:01 2012 +0100 +++ b/OrthancServer/DatabaseWrapper.cpp Wed Dec 19 14:57:18 2012 +0100 @@ -275,6 +275,22 @@ return s.ColumnString(0); } + + ResourceType DatabaseWrapper::GetResourceType(int64_t resourceId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT resourceType FROM Resources WHERE internalId=?"); + s.BindInt(0, resourceId); + + if (!s.Step()) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + return static_cast(s.ColumnInt(0)); + } + + void DatabaseWrapper::AttachChild(int64_t parent, int64_t child) { diff -r c76a35a85c69 -r 4eea080e6e7a OrthancServer/DatabaseWrapper.h --- a/OrthancServer/DatabaseWrapper.h Tue Dec 18 19:01:01 2012 +0100 +++ b/OrthancServer/DatabaseWrapper.h Wed Dec 19 14:57:18 2012 +0100 @@ -94,6 +94,8 @@ std::string GetPublicId(int64_t resourceId); + ResourceType GetResourceType(int64_t resourceId); + void AttachChild(int64_t parent, int64_t child); diff -r c76a35a85c69 -r 4eea080e6e7a OrthancServer/FromDcmtkBridge.cpp --- a/OrthancServer/FromDcmtkBridge.cpp Tue Dec 18 19:01:01 2012 +0100 +++ b/OrthancServer/FromDcmtkBridge.cpp Wed Dec 19 14:57:18 2012 +0100 @@ -82,12 +82,12 @@ namespace Orthanc { - ParsedDicomFile::ParsedDicomFile(const std::string& content) + void ParsedDicomFile::Setup(const char* buffer, size_t size) { DcmInputBufferStream is; - if (content.size() > 0) + if (size > 0) { - is.setBuffer(&content[0], content.size()); + is.setBuffer(buffer, size); } is.setEos(); @@ -141,29 +141,11 @@ GetCharValue(c[3])); } - static bool ParseTagAndGroup(DcmTagKey& key, + static void ParseTagAndGroup(DcmTagKey& key, const std::string& tag) { - if (tag.size() != 9 || - !isxdigit(tag[0]) || - !isxdigit(tag[1]) || - !isxdigit(tag[2]) || - !isxdigit(tag[3]) || - tag[4] != '-' || - !isxdigit(tag[5]) || - !isxdigit(tag[6]) || - !isxdigit(tag[7]) || - !isxdigit(tag[8])) - { - return false; - } - - uint16_t group = GetTagValue(tag.c_str()); - uint16_t element = GetTagValue(tag.c_str() + 5); - - key = DcmTagKey(group, element); - - return true; + DicomTag t = FromDcmtkBridge::ParseTag(tag); + key = DcmTagKey(t.GetGroup(), t.GetElement()); } @@ -224,10 +206,7 @@ DcmItem& dicom) { DcmTagKey k; - if (!ParseTagAndGroup(k, tag)) - { - return; - } + ParseTagAndGroup(k, tag); DcmSequenceOfItems* sequence = NULL; if (dicom.findAndGetSequence(k, sequence).good() && @@ -268,8 +247,8 @@ DcmTagKey k; DcmItem *child = NULL; - if (!ParseTagAndGroup(k, uri[2 * pos]) || - !dicom->findAndGetSequenceItem(k, child, index).good() || + ParseTagAndGroup(k, uri[2 * pos]); + if (!dicom->findAndGetSequenceItem(k, child, index).good() || child == NULL) { return; @@ -1158,8 +1137,24 @@ } - DicomTag FromDcmtkBridge::FindTag(const char* name) + DicomTag FromDcmtkBridge::ParseTag(const char* name) { + if (strlen(name) == 9 && + isxdigit(name[0]) && + isxdigit(name[1]) && + isxdigit(name[2]) && + isxdigit(name[3]) && + name[4] == '-' && + isxdigit(name[5]) && + isxdigit(name[6]) && + isxdigit(name[7]) && + isxdigit(name[8])) + { + uint16_t group = GetTagValue(name); + uint16_t element = GetTagValue(name + 5); + return DicomTag(group, element); + } + const DcmDataDictionary& dict = dcmDataDict.rdlock(); const DcmDictEntry* entry = dict.findEntry(name); diff -r c76a35a85c69 -r 4eea080e6e7a OrthancServer/FromDcmtkBridge.h --- a/OrthancServer/FromDcmtkBridge.h Tue Dec 18 19:01:01 2012 +0100 +++ b/OrthancServer/FromDcmtkBridge.h Wed Dec 19 14:57:18 2012 +0100 @@ -71,8 +71,23 @@ const std::string& value, bool insertOnAbsent); + void Setup(const char* content, + size_t size); + public: - ParsedDicomFile(const std::string& content); + ParsedDicomFile(const char* content, + size_t size) + { + Setup(content, size); + } + + ParsedDicomFile(const std::string& content) + { + if (content.size() == 0) + Setup(NULL, 0); + else + Setup(&content[0], content.size()); + } DcmFileFormat& GetDicom() { @@ -130,30 +145,30 @@ static std::string GetName(const DicomTag& tag); - static DicomTag FindTag(const char* name); + static DicomTag ParseTag(const char* name); - static DicomTag FindTag(const std::string& name) + static DicomTag ParseTag(const std::string& name) { - return FindTag(name.c_str()); + return ParseTag(name.c_str()); } static bool HasTag(const DicomMap& fields, const std::string& tagName) { - return fields.HasTag(FindTag(tagName)); + return fields.HasTag(ParseTag(tagName)); } static const DicomValue& GetValue(const DicomMap& fields, const std::string& tagName) { - return fields.GetValue(FindTag(tagName)); + return fields.GetValue(ParseTag(tagName)); } static void SetValue(DicomMap& target, const std::string& tagName, DicomValue* value) { - target.SetValue(FindTag(tagName), value); + target.SetValue(ParseTag(tagName), value); } static void Print(FILE* fp, diff -r c76a35a85c69 -r 4eea080e6e7a OrthancServer/OrthancRestApi.cpp --- a/OrthancServer/OrthancRestApi.cpp Tue Dec 18 19:01:01 2012 +0100 +++ b/OrthancServer/OrthancRestApi.cpp Wed Dec 19 14:57:18 2012 +0100 @@ -89,7 +89,7 @@ Json::Value::Members members = query.getMemberNames(); for (size_t i = 0; i < members.size(); i++) { - DicomTag t = FromDcmtkBridge::FindTag(members[i]); + DicomTag t = FromDcmtkBridge::ParseTag(members[i]); result.SetValue(t, query[members[i]].asString()); } @@ -765,46 +765,14 @@ LOG(INFO) << "Receiving a DICOM file of " << postData.size() << " bytes through HTTP"; - DcmFileFormat dicomFile; - - { - // Prepare an input stream for the memory buffer - DcmInputBufferStream is; - is.setBuffer(&postData[0], postData.size()); - is.setEos(); - - dicomFile.transferInit(); - if (!dicomFile.read(is).good()) - { - call.GetOutput().SignalError(Orthanc_HttpStatus_415_UnsupportedMediaType); - return; - } - dicomFile.loadAllDataIntoMemory(); - dicomFile.transferEnd(); - } - - DicomMap dicomSummary; - FromDcmtkBridge::Convert(dicomSummary, *dicomFile.getDataset()); - - DicomInstanceHasher hasher(dicomSummary); - - Json::Value dicomJson; - FromDcmtkBridge::ToJson(dicomJson, *dicomFile.getDataset()); - - StoreStatus status = StoreStatus_Failure; - if (postData.size() > 0) - { - status = context.Store - (reinterpret_cast(&postData[0]), postData.size(), - dicomSummary, dicomJson, ""); - } - + std::string publicId; + StoreStatus status = context.Store(publicId, postData); Json::Value result = Json::objectValue; if (status != StoreStatus_Failure) { - result["ID"] = hasher.HashInstance(); - result["Path"] = GetBasePath(ResourceType_Instance, hasher.HashInstance()); + result["ID"] = publicId; + result["Path"] = GetBasePath(ResourceType_Instance, publicId); } result["Status"] = ToString(status); @@ -812,6 +780,7 @@ } + // DICOM bridge ------------------------------------------------------------- static bool IsExistingModality(const OrthancRestApi::Modalities& modalities, @@ -870,31 +839,54 @@ - // Modification of DICOM tags ----------------------------------------------- + // Modification of DICOM instances ------------------------------------------ + + static void ModifyInstanceInternal(ParsedDicomFile& toModify, + const Json::Value& replacements) + { + if (!replacements.isObject()) + { + throw OrthancException(ErrorCode_BadRequest); + } + + Json::Value::Members members = replacements.getMemberNames(); - template - static void Modify(RestApi::PostCall& call) + for (size_t i = 0; i < members.size(); i++) + { + const std::string& name = members[i]; + std::string value = replacements[name].asString(); + + DicomTag tag = FromDcmtkBridge::ParseTag(name); + toModify.Replace(tag, value); + } + + // A new SOP instance UID is automatically generated + std::string instanceUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance); + toModify.Replace(DICOM_TAG_SOP_INSTANCE_UID, instanceUid); + } + + + static void ModifyInstance(RestApi::PostCall& call) { RETRIEVE_CONTEXT(call); std::string id = call.GetUriComponent("id", ""); ParsedDicomFile& dicom = context.GetDicomFile(id); - std::auto_ptr modified(dicom.Clone()); + Json::Value request; + if (call.ParseJsonRequest(request)) + { + std::auto_ptr modified(dicom.Clone()); + ModifyInstanceInternal(*modified, request); + modified->Answer(call.GetOutput()); + } - std::string studyUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study); + /*std::string studyUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study); std::string seriesUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series); - std::string instanceUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance); - - modified->Replace(DICOM_TAG_SOP_INSTANCE_UID, instanceUid); modified->Replace(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid); - modified->Replace(DICOM_TAG_STUDY_INSTANCE_UID, studyUid); + modified->Replace(DICOM_TAG_STUDY_INSTANCE_UID, studyUid);*/ - modified->InsertOrReplace(DicomTag(0x0010,0x0010), "0.42"); - //modified->Remove(DicomTag(0x0010,0x0020)); - /*modified->Insert(DicomTag(0x0018,0x9082), "0.42"); - modified->Replace(DicomTag(0x0010,0x0010), "Hello");*/ - modified->Answer(call.GetOutput()); + } @@ -954,6 +946,6 @@ Register("/modalities/{id}/find", DicomFind); Register("/modalities/{id}/store", DicomStore); - Register("/instances/{id}/modify", Modify); + Register("/instances/{id}/modify", ModifyInstance); } } diff -r c76a35a85c69 -r 4eea080e6e7a OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Tue Dec 18 19:01:01 2012 +0100 +++ b/OrthancServer/ServerContext.cpp Wed Dec 19 14:57:18 2012 +0100 @@ -76,7 +76,7 @@ storage_.Remove(fileUuid); } - StoreStatus ServerContext::Store(const char* dicomFile, + StoreStatus ServerContext::Store(const char* dicomInstance, size_t dicomSize, const DicomMap& dicomSummary, const Json::Value& dicomJson, @@ -91,7 +91,7 @@ accessor_.SetCompressionForNextOperations(CompressionType_None); } - FileInfo dicomInfo = accessor_.Write(dicomFile, dicomSize, FileContentType_Dicom); + FileInfo dicomInfo = accessor_.Write(dicomInstance, dicomSize, FileContentType_Dicom); FileInfo jsonInfo = accessor_.Write(dicomJson.toStyledString(), FileContentType_Json); ServerIndex::Attachments attachments; @@ -188,4 +188,54 @@ return dynamic_cast(dicomCache_.Access(instancePublicId)); #endif } + + + StoreStatus ServerContext::Store(std::string& resultPublicId, + DcmFileFormat& dicomInstance, + const char* dicomBuffer, + size_t dicomSize) + { + DicomMap dicomSummary; + FromDcmtkBridge::Convert(dicomSummary, *dicomInstance.getDataset()); + + DicomInstanceHasher hasher(dicomSummary); + resultPublicId = hasher.HashInstance(); + + Json::Value dicomJson; + FromDcmtkBridge::ToJson(dicomJson, *dicomInstance.getDataset()); + + StoreStatus status = StoreStatus_Failure; + if (dicomSize > 0) + { + status = Store(dicomBuffer, dicomSize, dicomSummary, dicomJson, ""); + } + + return status; + } + + + StoreStatus ServerContext::Store(std::string& resultPublicId, + DcmFileFormat& dicomInstance) + { + std::string buffer; + if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, dicomInstance.getDataset())) + { + throw OrthancException(ErrorCode_InternalError); + } + + if (buffer.size() == 0) + return Store(resultPublicId, dicomInstance, NULL, 0); + else + return Store(resultPublicId, dicomInstance, &buffer[0], buffer.size()); + } + + + StoreStatus ServerContext::Store(std::string& resultPublicId, + const char* dicomBuffer, + size_t dicomSize) + { + ParsedDicomFile dicom(dicomBuffer, dicomSize); + return Store(resultPublicId, dicom.GetDicom(), dicomBuffer, dicomSize); + } + } diff -r c76a35a85c69 -r 4eea080e6e7a OrthancServer/ServerContext.h --- a/OrthancServer/ServerContext.h Tue Dec 18 19:01:01 2012 +0100 +++ b/OrthancServer/ServerContext.h Wed Dec 19 14:57:18 2012 +0100 @@ -87,12 +87,33 @@ void RemoveFile(const std::string& fileUuid); - StoreStatus Store(const char* dicomFile, + StoreStatus Store(const char* dicomInstance, size_t dicomSize, const DicomMap& dicomSummary, const Json::Value& dicomJson, const std::string& remoteAet); + StoreStatus Store(std::string& resultPublicId, + DcmFileFormat& dicomInstance, + const char* dicomBuffer, + size_t dicomSize); + + StoreStatus Store(std::string& resultPublicId, + DcmFileFormat& dicomInstance); + + StoreStatus Store(std::string& resultPublicId, + const char* dicomBuffer, + size_t dicomSize); + + StoreStatus Store(std::string& resultPublicId, + const std::string& dicomContent) + { + if (dicomContent.size() == 0) + return Store(resultPublicId, NULL, 0); + else + return Store(resultPublicId, &dicomContent[0], dicomContent.size()); + } + void AnswerFile(RestApiOutput& output, const std::string& instancePublicId, FileContentType content); diff -r c76a35a85c69 -r 4eea080e6e7a OrthancServer/ServerIndex.cpp --- a/OrthancServer/ServerIndex.cpp Tue Dec 18 19:01:01 2012 +0100 +++ b/OrthancServer/ServerIndex.cpp Wed Dec 19 14:57:18 2012 +0100 @@ -962,4 +962,54 @@ LOG(INFO) << "Patient " << publicId << " has been unprotected"; } + + void ServerIndex::GetChildInstances(std::list& result, + const std::string& publicId) + { + result.clear(); + + boost::mutex::scoped_lock lock(mutex_); + + ResourceType type; + int64_t top; + if (!db_->LookupResource(publicId, top, type)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + if (type == ResourceType_Instance) + { + // The resource is already an instance: Do not go down the hierarchy + result.push_back(publicId); + return; + } + + std::stack toExplore; + toExplore.push(top); + + std::list tmp; + + while (!toExplore.empty()) + { + // Get the internal ID of the current resource + int64_t resource = toExplore.top(); + toExplore.pop(); + + if (db_->GetResourceType(resource) == ResourceType_Instance) + { + result.push_back(db_->GetPublicId(resource)); + } + else + { + // Tag all the children of this resource as to be explored + db_->GetChildrenInternalId(tmp, resource); + for (std::list::const_iterator + it = tmp.begin(); it != tmp.end(); it++) + { + toExplore.push(*it); + } + } + } + } + } diff -r c76a35a85c69 -r 4eea080e6e7a OrthancServer/ServerIndex.h --- a/OrthancServer/ServerIndex.h Tue Dec 18 19:01:01 2012 +0100 +++ b/OrthancServer/ServerIndex.h Wed Dec 19 14:57:18 2012 +0100 @@ -142,5 +142,9 @@ void SetProtectedPatient(const std::string& publicId, bool isProtected); + + void GetChildInstances(std::list& result, + const std::string& publicId); + }; } diff -r c76a35a85c69 -r 4eea080e6e7a UnitTests/ServerIndex.cpp --- a/UnitTests/ServerIndex.cpp Tue Dec 18 19:01:01 2012 +0100 +++ b/UnitTests/ServerIndex.cpp Wed Dec 19 14:57:18 2012 +0100 @@ -63,6 +63,14 @@ ASSERT_EQ("f", index.GetPublicId(a[5])); ASSERT_EQ("g", index.GetPublicId(a[6])); + ASSERT_EQ(ResourceType_Patient, index.GetResourceType(a[0])); + ASSERT_EQ(ResourceType_Study, index.GetResourceType(a[1])); + ASSERT_EQ(ResourceType_Series, index.GetResourceType(a[2])); + ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[3])); + ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[4])); + ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[5])); + ASSERT_EQ(ResourceType_Study, index.GetResourceType(a[6])); + { Json::Value t; index.GetAllPublicIds(t, ResourceType_Patient); diff -r c76a35a85c69 -r 4eea080e6e7a UnitTests/main.cpp --- a/UnitTests/main.cpp Tue Dec 18 19:01:01 2012 +0100 +++ b/UnitTests/main.cpp Wed Dec 19 14:57:18 2012 +0100 @@ -101,9 +101,13 @@ { ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010))); - DicomTag t = FromDcmtkBridge::FindTag("SeriesDescription"); + DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription"); ASSERT_EQ(0x0008, t.GetGroup()); ASSERT_EQ(0x103E, t.GetElement()); + + t = FromDcmtkBridge::ParseTag("0020-e040"); + ASSERT_EQ(0x0020, t.GetGroup()); + ASSERT_EQ(0xe040, t.GetElement()); }