# HG changeset patch # User Sebastien Jodogne # Date 1356018290 -3600 # Node ID 326d5a4a5af3e48bcafb42eab147e91282b86b99 # Parent 86bb79522f19fb9cdeecb56db5c7369ce54f315f modification of instances diff -r 86bb79522f19 -r 326d5a4a5af3 NEWS --- a/NEWS Thu Dec 20 13:01:46 2012 +0100 +++ b/NEWS Thu Dec 20 16:44:50 2012 +0100 @@ -1,6 +1,8 @@ Pending changes in the mainline =============================== +* Support for the private tags + Version 0.4.0 (2012/12/14) ========================== diff -r 86bb79522f19 -r 326d5a4a5af3 OrthancServer/FromDcmtkBridge.cpp --- a/OrthancServer/FromDcmtkBridge.cpp Thu Dec 20 13:01:46 2012 +0100 +++ b/OrthancServer/FromDcmtkBridge.cpp Thu Dec 20 16:44:50 2012 +0100 @@ -39,10 +39,12 @@ #include "../Core/Toolbox.h" #include "../Core/OrthancException.h" #include "../Core/PngWriter.h" +#include "../Core/Uuid.h" #include "../Core/DicomFormat/DicomString.h" #include "../Core/DicomFormat/DicomNullValue.h" #include "../Core/DicomFormat/DicomIntegerPixelAccessor.h" +#include #include #include @@ -550,9 +552,41 @@ void ParsedDicomFile::Remove(const DicomTag& tag) { DcmTagKey key(tag.GetGroup(), tag.GetElement()); + DcmElement* element = file_->getDataset()->remove(key); + if (element != NULL) + { + delete element; + } + } - // TODO This call results in memory leaks inside DCMTK - file_->getDataset()->remove(key); + + + void ParsedDicomFile::RemovePrivateTags() + { + typedef std::list Tags; + + Tags privateTags; + + DcmDataset& dataset = *file_->getDataset(); + for (unsigned long i = 0; i < dataset.card(); i++) + { + DcmElement* element = dataset.getElement(i); + DcmTag tag(element->getTag()); + if (tag.getPrivateCreator() != NULL) + { + privateTags.push_back(element); + } + } + + for (Tags::iterator it = privateTags.begin(); + it != privateTags.end(); it++) + { + DcmElement* tmp = dataset.remove(*it); + if (tmp != NULL) + { + delete tmp; + } + } } @@ -681,26 +715,25 @@ /** * TODO. **/ - - case EVR_DS: // decimal string - case EVR_IS: // integer string + case EVR_OB: // other byte case EVR_OF: // other float case EVR_OW: // other word - case EVR_AS: // age string case EVR_AT: // attribute tag - case EVR_DA: // date string - case EVR_DT: // date time string - case EVR_TM: // time string case EVR_UN: // unknown value representation return new DicomNullValue(); - - + /** * String types, should never happen at this point because of * "element.isaString()". **/ + case EVR_DS: // decimal string + case EVR_IS: // integer string + case EVR_AS: // age string + case EVR_DA: // date string + case EVR_DT: // date time string + case EVR_TM: // time string case EVR_AE: // application entity title case EVR_CS: // code string case EVR_SH: // short string @@ -721,78 +754,54 @@ { Sint32 f; if (dynamic_cast(element).getSint32(f).good()) - { return new DicomString(boost::lexical_cast(f)); - } else - { return new DicomNullValue(); - } } case EVR_SS: // signed short { Sint16 f; if (dynamic_cast(element).getSint16(f).good()) - { return new DicomString(boost::lexical_cast(f)); - } else - { return new DicomNullValue(); - } } case EVR_UL: // unsigned long { Uint32 f; if (dynamic_cast(element).getUint32(f).good()) - { return new DicomString(boost::lexical_cast(f)); - } else - { return new DicomNullValue(); - } } case EVR_US: // unsigned short { Uint16 f; if (dynamic_cast(element).getUint16(f).good()) - { return new DicomString(boost::lexical_cast(f)); - } else - { return new DicomNullValue(); - } } case EVR_FL: // float single-precision { Float32 f; if (dynamic_cast(element).getFloat32(f).good()) - { return new DicomString(boost::lexical_cast(f)); - } else - { return new DicomNullValue(); - } } case EVR_FD: // float double-precision { Float64 f; if (dynamic_cast(element).getFloat64(f).good()) - { return new DicomString(boost::lexical_cast(f)); - } else - { return new DicomNullValue(); - } } @@ -841,6 +850,10 @@ { return new DicomNullValue; } + catch (std::bad_cast) + { + return new DicomNullValue; + } } @@ -876,7 +889,7 @@ #else // This version of the code gives access to the name of the private tags DcmTag tagbis(element.getTag()); - const std::string tagName(tagbis.getTagName()); + const std::string tagName(tagbis.getTagName()); #endif if (element.isLeaf()) @@ -884,6 +897,11 @@ Json::Value value(Json::objectValue); value["Name"] = tagName; + if (tagbis.getPrivateCreator() != NULL) + { + value["PrivateCreator"] = tagbis.getPrivateCreator(); + } + std::auto_ptr v(FromDcmtkBridge::ConvertLeafElement(element)); if (v->IsNull()) { @@ -1124,7 +1142,7 @@ const DcmDataDictionary& dict = dcmDataDict.rdlock(); const DcmDictEntry* entry = dict.findEntry(tag, NULL); - std::string s("Unknown"); + std::string s(DcmTag_ERROR_TagName); if (entry != NULL) { s = std::string(entry->getTagName()); @@ -1137,7 +1155,7 @@ const char* name = tag.getTagName(); if (name == NULL) { - return "Unknown"; + return DcmTag_ERROR_TagName; } else { @@ -1231,6 +1249,19 @@ switch (level) { + case DicomRootLevel_Patient: + { + std::string uuid = Toolbox::GenerateUuid(); + std::string id; + id.reserve(uuid.size()); + for (size_t i = 0; i < uuid.size() && i < 8; i++) + { + id.push_back(toupper(uuid[i])); + } + + return id; + } + case DicomRootLevel_Instance: return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT); diff -r 86bb79522f19 -r 326d5a4a5af3 OrthancServer/FromDcmtkBridge.h --- a/OrthancServer/FromDcmtkBridge.h Thu Dec 20 13:01:46 2012 +0100 +++ b/OrthancServer/FromDcmtkBridge.h Thu Dec 20 16:44:50 2012 +0100 @@ -52,6 +52,7 @@ enum DicomRootLevel { + DicomRootLevel_Patient, DicomRootLevel_Study, DicomRootLevel_Series, DicomRootLevel_Instance @@ -115,6 +116,8 @@ void Replace(const DicomTag& tag, const std::string& value, DicomReplaceMode mode); + + void RemovePrivateTags(); }; class FromDcmtkBridge diff -r 86bb79522f19 -r 326d5a4a5af3 OrthancServer/OrthancRestApi.cpp --- a/OrthancServer/OrthancRestApi.cpp Thu Dec 20 13:01:46 2012 +0100 +++ b/OrthancServer/OrthancRestApi.cpp Thu Dec 20 16:44:50 2012 +0100 @@ -842,16 +842,23 @@ // Modification of DICOM instances ------------------------------------------ static void ReplaceInstanceInternal(ParsedDicomFile& toModify, + const Json::Value& removals, const Json::Value& replacements, DicomReplaceMode mode) { - if (!replacements.isObject()) + if (!replacements.isObject() || + !removals.isArray()) { throw OrthancException(ErrorCode_BadRequest); } + for (Json::Value::ArrayIndex i = 0; i < removals.size(); i++) + { + DicomTag tag = FromDcmtkBridge::ParseTag(removals[i].asString()); + toModify.Remove(tag); + } + Json::Value::Members members = replacements.getMemberNames(); - for (size_t i = 0; i < members.size(); i++) { const std::string& name = members[i]; @@ -866,8 +873,35 @@ toModify.Replace(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, DicomReplaceMode_InsertIfAbsent); } + + static bool ParseModifyRequest(Json::Value& removals, + Json::Value& replacements, + const RestApi::PostCall& call) + { + Json::Value request; + if (call.ParseJsonRequest(request) && + request.isObject()) + { + removals = Json::arrayValue; + replacements = Json::objectValue; - + if (request.isMember("Remove")) + { + removals = request["Remove"]; + } + + if (request.isMember("Replace")) + { + replacements = request["Replace"]; + } + + return true; + } + else + { + return false; + } + } static void ModifyInstance(RestApi::PostCall& call) @@ -877,18 +911,98 @@ std::string id = call.GetUriComponent("id", ""); ParsedDicomFile& dicom = context.GetDicomFile(id); - Json::Value request; - if (call.ParseJsonRequest(request)) + Json::Value removals, replacements; + if (ParseModifyRequest(removals, replacements, call)) { std::auto_ptr modified(dicom.Clone()); - ReplaceInstanceInternal(*modified, request, DicomReplaceMode_InsertIfAbsent); + ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent); + context.GetIndex().SetMetadata(id, MetadataType_ModifiedFrom, id); modified->Answer(call.GetOutput()); } + } + + + template + static void ModifyInplace(RestApi::PostCall& call) + { + typedef std::list Instances; + + RETRIEVE_CONTEXT(call); + + Instances instances; + std::string id = call.GetUriComponent("id", ""); + context.GetIndex().GetChildInstances(instances, id); + + if (instances.size() == 0) + { + return; + } - /*std::string studyUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study); - std::string seriesUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series); - modified->Replace(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid); - modified->Replace(DICOM_TAG_STUDY_INSTANCE_UID, studyUid);*/ + Json::Value removals, replacements; + if (ParseModifyRequest(removals, replacements, call)) + { + switch (resourceType) + { + // DO NOT ADD "break" OR CHANGE THE ORDER BELOW + case ResourceType_Patient: + replacements["0010-0020"] = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient); + + case ResourceType_Study: + replacements["0020-000d"] = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study); + + case ResourceType_Series: + replacements["0020-000e"] = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + std::string modifiedId; + for (Instances::const_iterator it = instances.begin(); + it != instances.end(); it++) + { + LOG(INFO) << "Modifying instance " << *it; + ParsedDicomFile& dicom = context.GetDicomFile(*it); + std::auto_ptr modified(dicom.Clone()); + ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent); + + if (context.Store(modifiedId, modified->GetDicom()) != StoreStatus_Success) + { + LOG(ERROR) << "Error while modifying the instance " << *it; + return; + } + + context.GetIndex().SetMetadata(modifiedId, MetadataType_ModifiedFrom, *it); + } + + + int level; + std::string id; + switch (resourceType) + { + case ResourceType_Series: level = 1; break; + case ResourceType_Study: level = 2; break; + case ResourceType_Patient: level = 3; break; + default: + throw OrthancException(ErrorCode_InternalError); + } + + for (int i = 0; i < level; i++) + { + if (!context.GetIndex().LookupParent(id, modifiedId)) + { + throw OrthancException(ErrorCode_InternalError); + } + + modifiedId = id; + } + + Json::Value result = Json::objectValue; + result["ID"] = id; + result["Path"] = GetBasePath(resourceType, id); + call.GetOutput().AnswerJson(result); + } } @@ -949,5 +1063,8 @@ Register("/modalities/{id}/store", DicomStore); Register("/instances/{id}/modify", ModifyInstance); + Register("/series/{id}/modify", ModifyInplace); + Register("/studies/{id}/modify", ModifyInplace); + Register("/patients/{id}/modify", ModifyInplace); } } diff -r 86bb79522f19 -r 326d5a4a5af3 OrthancServer/ServerEnumerations.h --- a/OrthancServer/ServerEnumerations.h Thu Dec 20 13:01:46 2012 +0100 +++ b/OrthancServer/ServerEnumerations.h Thu Dec 20 16:44:50 2012 +0100 @@ -76,7 +76,8 @@ MetadataType_Instance_IndexInSeries = 1, MetadataType_Instance_ReceptionDate = 2, MetadataType_Instance_RemoteAet = 3, - MetadataType_Series_ExpectedNumberOfInstances = 4 + MetadataType_Series_ExpectedNumberOfInstances = 4, + MetadataType_ModifiedFrom = 5 }; enum ChangeType diff -r 86bb79522f19 -r 326d5a4a5af3 OrthancServer/ServerIndex.cpp --- a/OrthancServer/ServerIndex.cpp Thu Dec 20 13:01:46 2012 +0100 +++ b/OrthancServer/ServerIndex.cpp Thu Dec 20 16:44:50 2012 +0100 @@ -1012,4 +1012,61 @@ } } + + void ServerIndex::SetMetadata(const std::string& publicId, + MetadataType type, + const std::string& value) + { + boost::mutex::scoped_lock lock(mutex_); + + ResourceType rtype; + int64_t id; + if (!db_->LookupResource(publicId, id, rtype)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + db_->SetMetadata(id, type, value); + } + + bool ServerIndex::LookupMetadata(std::string& target, + const std::string& publicId, + MetadataType type) + { + boost::mutex::scoped_lock lock(mutex_); + + ResourceType rtype; + int64_t id; + if (!db_->LookupResource(publicId, id, rtype)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + return db_->LookupMetadata(target, id, type); + } + + + bool ServerIndex::LookupParent(std::string& target, + const std::string& publicId) + { + boost::mutex::scoped_lock lock(mutex_); + + ResourceType type; + int64_t id; + if (!db_->LookupResource(publicId, id, type)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + int64_t parentId; + if (db_->LookupParent(parentId, id)) + { + target = db_->GetPublicId(parentId); + return true; + } + else + { + return false; + } + } } diff -r 86bb79522f19 -r 326d5a4a5af3 OrthancServer/ServerIndex.h --- a/OrthancServer/ServerIndex.h Thu Dec 20 13:01:46 2012 +0100 +++ b/OrthancServer/ServerIndex.h Thu Dec 20 16:44:50 2012 +0100 @@ -146,5 +146,15 @@ void GetChildInstances(std::list& result, const std::string& publicId); + void SetMetadata(const std::string& publicId, + MetadataType type, + const std::string& value); + + bool LookupMetadata(std::string& target, + const std::string& publicId, + MetadataType type); + + bool LookupParent(std::string& target, + const std::string& publicId); }; }