Mercurial > hg > orthanc
changeset 790:331eaf9d9d69
ParsedDicomFile
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 05 May 2014 18:55:10 +0200 |
parents | 55dae8c5a6ab |
children | 381f90e2b69d |
files | CMakeLists.txt OrthancServer/DicomModification.cpp OrthancServer/DicomModification.h OrthancServer/FromDcmtkBridge.cpp OrthancServer/FromDcmtkBridge.h OrthancServer/ParsedDicomFile.cpp OrthancServer/ParsedDicomFile.h OrthancServer/ServerContext.h |
diffstat | 8 files changed, 1154 insertions(+), 826 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Mon May 05 18:43:34 2014 +0200 +++ b/CMakeLists.txt Mon May 05 18:55:10 2014 +0200 @@ -206,6 +206,7 @@ OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp OrthancServer/DicomModification.cpp OrthancServer/FromDcmtkBridge.cpp + OrthancServer/ParsedDicomFile.cpp OrthancServer/Internals/CommandDispatcher.cpp OrthancServer/Internals/FindScp.cpp OrthancServer/Internals/MoveScp.cpp
--- a/OrthancServer/DicomModification.cpp Mon May 05 18:43:34 2014 +0200 +++ b/OrthancServer/DicomModification.cpp Mon May 05 18:55:10 2014 +0200 @@ -33,6 +33,7 @@ #include "DicomModification.h" #include "../Core/OrthancException.h" +#include "FromDcmtkBridge.h" namespace Orthanc {
--- a/OrthancServer/DicomModification.h Mon May 05 18:43:34 2014 +0200 +++ b/OrthancServer/DicomModification.h Mon May 05 18:55:10 2014 +0200 @@ -32,7 +32,7 @@ #pragma once -#include "FromDcmtkBridge.h" +#include "ParsedDicomFile.h" namespace Orthanc {
--- a/OrthancServer/FromDcmtkBridge.cpp Mon May 05 18:43:34 2014 +0200 +++ b/OrthancServer/FromDcmtkBridge.cpp Mon May 05 18:55:10 2014 +0200 @@ -131,51 +131,8 @@ #include <dcmtk/dcmdata/dcostrmb.h> -static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"; - - - namespace Orthanc { - void ParsedDicomFile::Setup(const char* buffer, size_t size) - { - DcmInputBufferStream is; - if (size > 0) - { - is.setBuffer(buffer, size); - } - is.setEos(); - - file_.reset(new DcmFileFormat); - file_->transferInit(); - if (!file_->read(is).good()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - file_->loadAllDataIntoMemory(); - file_->transferEnd(); - } - - - static void SendPathValueForDictionary(RestApiOutput& output, - DcmItem& dicom) - { - Json::Value v = Json::arrayValue; - - for (unsigned long i = 0; i < dicom.card(); i++) - { - DcmElement* element = dicom.getElement(i); - if (element) - { - char buf[16]; - sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag()); - v.append(buf); - } - } - - output.AnswerJson(v); - } - static inline uint16_t GetCharValue(char c) { if (c >= '0' && c <= '9') @@ -196,698 +153,6 @@ GetCharValue(c[3])); } - static void ParseTagAndGroup(DcmTagKey& key, - const std::string& tag) - { - DicomTag t = FromDcmtkBridge::ParseTag(tag); - key = DcmTagKey(t.GetGroup(), t.GetElement()); - } - - - static void SendSequence(RestApiOutput& output, - DcmSequenceOfItems& sequence) - { - // This element is a sequence - Json::Value v = Json::arrayValue; - - for (unsigned long i = 0; i < sequence.card(); i++) - { - v.append(boost::lexical_cast<std::string>(i)); - } - - output.AnswerJson(v); - } - - - static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData, - E_TransferSyntax transferSyntax) - { - DcmPixelSequence* pixelSequence = NULL; - if (pixelData.getEncapsulatedRepresentation - (transferSyntax, NULL, pixelSequence).good() && pixelSequence) - { - return pixelSequence->card(); - } - else - { - return 1; - } - } - - - static void AnswerDicomField(RestApiOutput& output, - DcmElement& element, - E_TransferSyntax transferSyntax) - { - // This element is nor a sequence, neither a pixel-data - std::string buffer; - buffer.resize(65536); - Uint32 length = element.getLength(transferSyntax); - Uint32 offset = 0; - - output.GetLowLevelOutput().SendOkHeader(CONTENT_TYPE_OCTET_STREAM, true, length, NULL); - - while (offset < length) - { - Uint32 nbytes; - if (length - offset < buffer.size()) - { - nbytes = length - offset; - } - else - { - nbytes = buffer.size(); - } - - OFCondition cond = element.getPartialValue(&buffer[0], offset, nbytes); - - if (cond.good()) - { - output.GetLowLevelOutput().Send(&buffer[0], nbytes); - offset += nbytes; - } - else - { - LOG(ERROR) << "Error while sending a DICOM field: " << cond.text(); - return; - } - } - - output.MarkLowLevelOutputDone(); - } - - - static bool AnswerPixelData(RestApiOutput& output, - DcmItem& dicom, - E_TransferSyntax transferSyntax, - const std::string* blockUri) - { - DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(), - DICOM_TAG_PIXEL_DATA.GetElement()); - - DcmElement *element = NULL; - if (!dicom.findAndGetElement(k, element).good() || - element == NULL) - { - return false; - } - - try - { - DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element); - if (blockUri == NULL) - { - // The user asks how many blocks are presents in this pixel data - unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax); - - Json::Value result(Json::arrayValue); - for (unsigned int i = 0; i < blocks; i++) - { - result.append(boost::lexical_cast<std::string>(i)); - } - - output.AnswerJson(result); - return true; - } - - - unsigned int block = boost::lexical_cast<unsigned int>(*blockUri); - - if (block < GetPixelDataBlockCount(pixelData, transferSyntax)) - { - DcmPixelSequence* pixelSequence = NULL; - if (pixelData.getEncapsulatedRepresentation - (transferSyntax, NULL, pixelSequence).good() && pixelSequence) - { - // This is the case for JPEG transfer syntaxes - if (block < pixelSequence->card()) - { - DcmPixelItem* pixelItem = NULL; - if (pixelSequence->getItem(pixelItem, block).good() && pixelItem) - { - if (pixelItem->getLength() == 0) - { - output.AnswerBuffer(NULL, 0, CONTENT_TYPE_OCTET_STREAM); - return true; - } - - Uint8* buffer = NULL; - if (pixelItem->getUint8Array(buffer).good() && buffer) - { - output.AnswerBuffer(buffer, pixelItem->getLength(), CONTENT_TYPE_OCTET_STREAM); - return true; - } - } - } - } - else - { - // This is the case for raw, uncompressed image buffers - assert(*blockUri == "0"); - AnswerDicomField(output, *element, transferSyntax); - } - } - } - catch (boost::bad_lexical_cast&) - { - // The URI entered by the user is not a number - } - catch (std::bad_cast&) - { - // This should never happen - } - - return false; - } - - - - static void SendPathValueForLeaf(RestApiOutput& output, - const std::string& tag, - DcmItem& dicom, - E_TransferSyntax transferSyntax) - { - DcmTagKey k; - ParseTagAndGroup(k, tag); - - DcmSequenceOfItems* sequence = NULL; - if (dicom.findAndGetSequence(k, sequence).good() && - sequence != NULL && - sequence->getVR() == EVR_SQ) - { - SendSequence(output, *sequence); - return; - } - - DcmElement* element = NULL; - if (dicom.findAndGetElement(k, element).good() && - element != NULL && - //element->getVR() != EVR_UNKNOWN && // This would forbid private tags - element->getVR() != EVR_SQ) - { - AnswerDicomField(output, *element, transferSyntax); - } - } - - void ParsedDicomFile::SendPathValue(RestApiOutput& output, - const UriComponents& uri) - { - DcmItem* dicom = file_->getDataset(); - E_TransferSyntax transferSyntax = file_->getDataset()->getOriginalXfer(); - - // Special case: Accessing the pixel data - if (uri.size() == 1 || - uri.size() == 2) - { - DcmTagKey tag; - ParseTagAndGroup(tag, uri[0]); - - if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() && - tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement()) - { - AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]); - return; - } - } - - // Go down in the tag hierarchy according to the URI - for (size_t pos = 0; pos < uri.size() / 2; pos++) - { - size_t index; - try - { - index = boost::lexical_cast<size_t>(uri[2 * pos + 1]); - } - catch (boost::bad_lexical_cast&) - { - return; - } - - DcmTagKey k; - DcmItem *child = NULL; - ParseTagAndGroup(k, uri[2 * pos]); - if (!dicom->findAndGetSequenceItem(k, child, index).good() || - child == NULL) - { - return; - } - - dicom = child; - } - - // We have reached the end of the URI - if (uri.size() % 2 == 0) - { - SendPathValueForDictionary(output, *dicom); - } - else - { - SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax); - } - } - - - - - - static DcmElement* CreateElementForTag(const DicomTag& tag) - { - DcmTag key(tag.GetGroup(), tag.GetElement()); - - switch (key.getEVR()) - { - // http://support.dcmtk.org/docs/dcvr_8h-source.html - - /** - * TODO. - **/ - - case EVR_OB: // other byte - case EVR_OF: // other float - case EVR_OW: // other word - case EVR_AT: // attribute tag - throw OrthancException(ErrorCode_NotImplemented); - - case EVR_UN: // unknown value representation - throw OrthancException(ErrorCode_ParameterOutOfRange); - - - /** - * String types. - * http://support.dcmtk.org/docs/classDcmByteString.html - **/ - - case EVR_AS: // age string - return new DcmAgeString(key); - - case EVR_AE: // application entity title - return new DcmApplicationEntity(key); - - case EVR_CS: // code string - return new DcmCodeString(key); - - case EVR_DA: // date string - return new DcmDate(key); - - case EVR_DT: // date time string - return new DcmDateTime(key); - - case EVR_DS: // decimal string - return new DcmDecimalString(key); - - case EVR_IS: // integer string - return new DcmIntegerString(key); - - case EVR_TM: // time string - return new DcmTime(key); - - case EVR_UI: // unique identifier - return new DcmUniqueIdentifier(key); - - case EVR_ST: // short text - return new DcmShortText(key); - - case EVR_LO: // long string - return new DcmLongString(key); - - case EVR_LT: // long text - return new DcmLongText(key); - - case EVR_UT: // unlimited text - return new DcmUnlimitedText(key); - - case EVR_SH: // short string - return new DcmShortString(key); - - case EVR_PN: // person name - return new DcmPersonName(key); - - - /** - * Numerical types - **/ - - case EVR_SL: // signed long - return new DcmSignedLong(key); - - case EVR_SS: // signed short - return new DcmSignedShort(key); - - case EVR_UL: // unsigned long - return new DcmUnsignedLong(key); - - case EVR_US: // unsigned short - return new DcmUnsignedShort(key); - - case EVR_FL: // float single-precision - return new DcmFloatingPointSingle(key); - - case EVR_FD: // float double-precision - return new DcmFloatingPointDouble(key); - - - /** - * Sequence types, should never occur at this point. - **/ - - case EVR_SQ: // sequence of items - throw OrthancException(ErrorCode_ParameterOutOfRange); - - - /** - * Internal to DCMTK. - **/ - - case EVR_ox: // OB or OW depending on context - case EVR_xs: // SS or US depending on context - case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) - case EVR_na: // na="not applicable", for data which has no VR - case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor - case EVR_item: // used internally for items - case EVR_metainfo: // used internally for meta info datasets - case EVR_dataset: // used internally for datasets - case EVR_fileFormat: // used internally for DICOM files - case EVR_dicomDir: // used internally for DICOMDIR objects - case EVR_dirRecord: // used internally for DICOMDIR records - case EVR_pixelSQ: // used internally for pixel sequences in a compressed image - case EVR_pixelItem: // used internally for pixel items in a compressed image - case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) - case EVR_PixelData: // used internally for uncompressed pixeld data - case EVR_OverlayData: // used internally for overlay data - case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR - default: - break; - } - - throw OrthancException(ErrorCode_InternalError); - } - - - - static void FillElementWithString(DcmElement& element, - const DicomTag& tag, - const std::string& value) - { - DcmTag key(tag.GetGroup(), tag.GetElement()); - bool ok = false; - - try - { - switch (key.getEVR()) - { - // http://support.dcmtk.org/docs/dcvr_8h-source.html - - /** - * TODO. - **/ - - case EVR_OB: // other byte - case EVR_OF: // other float - case EVR_OW: // other word - case EVR_AT: // attribute tag - throw OrthancException(ErrorCode_NotImplemented); - - case EVR_UN: // unknown value representation - throw OrthancException(ErrorCode_ParameterOutOfRange); - - - /** - * String types. - **/ - - 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 - case EVR_LO: // long string - case EVR_ST: // short text - case EVR_LT: // long text - case EVR_UT: // unlimited text - case EVR_PN: // person name - case EVR_UI: // unique identifier - { - ok = element.putString(value.c_str()).good(); - break; - } - - - /** - * Numerical types - **/ - - case EVR_SL: // signed long - { - ok = element.putSint32(boost::lexical_cast<Sint32>(value)).good(); - break; - } - - case EVR_SS: // signed short - { - ok = element.putSint16(boost::lexical_cast<Sint16>(value)).good(); - break; - } - - case EVR_UL: // unsigned long - { - ok = element.putUint32(boost::lexical_cast<Uint32>(value)).good(); - break; - } - - case EVR_US: // unsigned short - { - ok = element.putUint16(boost::lexical_cast<Uint16>(value)).good(); - break; - } - - case EVR_FL: // float single-precision - { - ok = element.putFloat32(boost::lexical_cast<float>(value)).good(); - break; - } - - case EVR_FD: // float double-precision - { - ok = element.putFloat64(boost::lexical_cast<double>(value)).good(); - break; - } - - - /** - * Sequence types, should never occur at this point. - **/ - - case EVR_SQ: // sequence of items - { - ok = false; - break; - } - - - /** - * Internal to DCMTK. - **/ - - case EVR_ox: // OB or OW depending on context - case EVR_xs: // SS or US depending on context - case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) - case EVR_na: // na="not applicable", for data which has no VR - case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor - case EVR_item: // used internally for items - case EVR_metainfo: // used internally for meta info datasets - case EVR_dataset: // used internally for datasets - case EVR_fileFormat: // used internally for DICOM files - case EVR_dicomDir: // used internally for DICOMDIR objects - case EVR_dirRecord: // used internally for DICOMDIR records - case EVR_pixelSQ: // used internally for pixel sequences in a compressed image - case EVR_pixelItem: // used internally for pixel items in a compressed image - case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) - case EVR_PixelData: // used internally for uncompressed pixeld data - case EVR_OverlayData: // used internally for overlay data - case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR - default: - break; - } - } - catch (boost::bad_lexical_cast&) - { - ok = false; - } - - if (!ok) - { - throw OrthancException(ErrorCode_InternalError); - } - } - - - void ParsedDicomFile::Remove(const DicomTag& tag) - { - DcmTagKey key(tag.GetGroup(), tag.GetElement()); - DcmElement* element = file_->getDataset()->remove(key); - if (element != NULL) - { - delete element; - } - } - - - - void ParsedDicomFile::RemovePrivateTags() - { - typedef std::list<DcmElement*> 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 (!strcmp("PrivateCreator", tag.getTagName()) || // TODO - This may change with future versions of DCMTK - 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; - } - } - } - - - - void ParsedDicomFile::Insert(const DicomTag& tag, - const std::string& value) - { - std::auto_ptr<DcmElement> element(CreateElementForTag(tag)); - FillElementWithString(*element, tag, value); - - if (!file_->getDataset()->insert(element.release(), false, false).good()) - { - // This field already exists - throw OrthancException(ErrorCode_InternalError); - } - } - - - void ParsedDicomFile::Replace(const DicomTag& tag, - const std::string& value, - DicomReplaceMode mode) - { - DcmTagKey key(tag.GetGroup(), tag.GetElement()); - DcmElement* element = NULL; - - if (!file_->getDataset()->findAndGetElement(key, element).good() || - element == NULL) - { - // This field does not exist, act wrt. the specified "mode" - switch (mode) - { - case DicomReplaceMode_InsertIfAbsent: - Insert(tag, value); - break; - - case DicomReplaceMode_ThrowIfAbsent: - throw OrthancException(ErrorCode_InexistentItem); - - case DicomReplaceMode_IgnoreIfAbsent: - return; - } - } - else - { - FillElementWithString(*element, tag, value); - } - - - /** - * dcmodify will automatically correct 'Media Storage SOP Class - * UID' and 'Media Storage SOP Instance UID' in the metaheader, if - * you make changes to the related tags in the dataset ('SOP Class - * UID' and 'SOP Instance UID') via insert or modify mode - * options. You can disable this behaviour by using the -nmu - * option. - **/ - - if (tag == DICOM_TAG_SOP_CLASS_UID) - { - Replace(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, value, DicomReplaceMode_InsertIfAbsent); - } - - if (tag == DICOM_TAG_SOP_INSTANCE_UID) - { - Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, value, DicomReplaceMode_InsertIfAbsent); - } - } - - - void ParsedDicomFile::Answer(RestApiOutput& output) - { - std::string serialized; - if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, file_->getDataset())) - { - output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM); - } - } - - - - bool ParsedDicomFile::GetTagValue(std::string& value, - const DicomTag& tag) - { - DcmTagKey k(tag.GetGroup(), tag.GetElement()); - DcmDataset& dataset = *file_->getDataset(); - DcmElement* element = NULL; - if (!dataset.findAndGetElement(k, element).good() || - element == NULL) - { - return false; - } - - std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element)); - - if (v.get() == NULL) - { - value = ""; - } - else - { - value = v->AsString(); - } - - return true; - } - - - - DicomInstanceHasher ParsedDicomFile::GetHasher() - { - std::string patientId, studyUid, seriesUid, instanceUid; - - if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID) || - !GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) || - !GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) || - !GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid); - } - - void FromDcmtkBridge::Convert(DicomMap& target, DcmDataset& dataset) { target.Clear(); @@ -1737,24 +1002,4 @@ return false; } } - - - void ParsedDicomFile::SaveToFile(const std::string& path) - { - // TODO Avoid using a temporary memory buffer, write directly on disk - std::string content; - SaveToMemoryBuffer(content); - Toolbox::WriteFile(content, path); - } - - - ParsedDicomFile::ParsedDicomFile() - { - file_.reset(new DcmFileFormat); - Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient)); - Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study)); - Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series)); - Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); - } - }
--- a/OrthancServer/FromDcmtkBridge.h Mon May 05 18:43:34 2014 +0200 +++ b/OrthancServer/FromDcmtkBridge.h Mon May 05 18:55:10 2014 +0200 @@ -110,74 +110,4 @@ static bool SaveToMemoryBuffer(std::string& buffer, DcmDataset* dataSet); }; - - class ParsedDicomFile : public IDynamicObject - { - private: - std::auto_ptr<DcmFileFormat> file_; - - ParsedDicomFile(DcmFileFormat& other) : - file_(dynamic_cast<DcmFileFormat*>(other.clone())) - { - } - - void Setup(const char* content, - size_t size); - - public: - ParsedDicomFile(); // Create a minimal DICOM instance - - 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() - { - return *file_; - } - - ParsedDicomFile* Clone() - { - return new ParsedDicomFile(*file_); - } - - void SendPathValue(RestApiOutput& output, - const UriComponents& uri); - - void Answer(RestApiOutput& output); - - void Remove(const DicomTag& tag); - - void Insert(const DicomTag& tag, - const std::string& value); - - void Replace(const DicomTag& tag, - const std::string& value, - DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent); - - void RemovePrivateTags(); - - bool GetTagValue(std::string& value, - const DicomTag& tag); - - DicomInstanceHasher GetHasher(); - - void SaveToMemoryBuffer(std::string& buffer) - { - FromDcmtkBridge::SaveToMemoryBuffer(buffer, file_->getDataset()); - } - - void SaveToFile(const std::string& path); - }; - }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ParsedDicomFile.cpp Mon May 05 18:55:10 2014 +0200 @@ -0,0 +1,1038 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + + +/*========================================================================= + + This file is based on portions of the following project: + + Program: GDCM (Grassroots DICOM). A DICOM library + Module: http://gdcm.sourceforge.net/Copyright.html + +Copyright (c) 2006-2011 Mathieu Malaterre +Copyright (c) 1993-2005 CREATIS +(CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any + contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "ParsedDicomFile.h" + +#include "FromDcmtkBridge.h" +#include "ToDcmtkBridge.h" +#include "../Core/Toolbox.h" +#include "../Core/OrthancException.h" +#include "../Core/FileFormats/PngWriter.h" +#include "../Core/Uuid.h" +#include "../Core/DicomFormat/DicomString.h" +#include "../Core/DicomFormat/DicomNullValue.h" +#include "../Core/DicomFormat/DicomIntegerPixelAccessor.h" + +#include <list> +#include <limits> + +#include <boost/lexical_cast.hpp> + +#include <dcmtk/dcmdata/dcchrstr.h> +#include <dcmtk/dcmdata/dcdicent.h> +#include <dcmtk/dcmdata/dcdict.h> +#include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcistrmb.h> +#include <dcmtk/dcmdata/dcuid.h> +#include <dcmtk/dcmdata/dcmetinf.h> + +#include <dcmtk/dcmdata/dcvrae.h> +#include <dcmtk/dcmdata/dcvras.h> +#include <dcmtk/dcmdata/dcvrcs.h> +#include <dcmtk/dcmdata/dcvrda.h> +#include <dcmtk/dcmdata/dcvrds.h> +#include <dcmtk/dcmdata/dcvrdt.h> +#include <dcmtk/dcmdata/dcvrfd.h> +#include <dcmtk/dcmdata/dcvrfl.h> +#include <dcmtk/dcmdata/dcvris.h> +#include <dcmtk/dcmdata/dcvrlo.h> +#include <dcmtk/dcmdata/dcvrlt.h> +#include <dcmtk/dcmdata/dcvrpn.h> +#include <dcmtk/dcmdata/dcvrsh.h> +#include <dcmtk/dcmdata/dcvrsl.h> +#include <dcmtk/dcmdata/dcvrss.h> +#include <dcmtk/dcmdata/dcvrst.h> +#include <dcmtk/dcmdata/dcvrtm.h> +#include <dcmtk/dcmdata/dcvrui.h> +#include <dcmtk/dcmdata/dcvrul.h> +#include <dcmtk/dcmdata/dcvrus.h> +#include <dcmtk/dcmdata/dcvrut.h> +#include <dcmtk/dcmdata/dcpixel.h> +#include <dcmtk/dcmdata/dcpixseq.h> +#include <dcmtk/dcmdata/dcpxitem.h> + + +#include <boost/math/special_functions/round.hpp> +#include <glog/logging.h> +#include <dcmtk/dcmdata/dcostrmb.h> + + +static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"; + + + +namespace Orthanc +{ + void ParsedDicomFile::Setup(const char* buffer, size_t size) + { + DcmInputBufferStream is; + if (size > 0) + { + is.setBuffer(buffer, size); + } + is.setEos(); + + file_.reset(new DcmFileFormat); + file_->transferInit(); + if (!file_->read(is).good()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + file_->loadAllDataIntoMemory(); + file_->transferEnd(); + } + + + static void SendPathValueForDictionary(RestApiOutput& output, + DcmItem& dicom) + { + Json::Value v = Json::arrayValue; + + for (unsigned long i = 0; i < dicom.card(); i++) + { + DcmElement* element = dicom.getElement(i); + if (element) + { + char buf[16]; + sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag()); + v.append(buf); + } + } + + output.AnswerJson(v); + } + + static inline uint16_t GetCharValue(char c) + { + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else + return 0; + } + + static inline uint16_t GetTagValue(const char* c) + { + return ((GetCharValue(c[0]) << 12) + + (GetCharValue(c[1]) << 8) + + (GetCharValue(c[2]) << 4) + + GetCharValue(c[3])); + } + + static void ParseTagAndGroup(DcmTagKey& key, + const std::string& tag) + { + DicomTag t = FromDcmtkBridge::ParseTag(tag); + key = DcmTagKey(t.GetGroup(), t.GetElement()); + } + + + static void SendSequence(RestApiOutput& output, + DcmSequenceOfItems& sequence) + { + // This element is a sequence + Json::Value v = Json::arrayValue; + + for (unsigned long i = 0; i < sequence.card(); i++) + { + v.append(boost::lexical_cast<std::string>(i)); + } + + output.AnswerJson(v); + } + + + static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData, + E_TransferSyntax transferSyntax) + { + DcmPixelSequence* pixelSequence = NULL; + if (pixelData.getEncapsulatedRepresentation + (transferSyntax, NULL, pixelSequence).good() && pixelSequence) + { + return pixelSequence->card(); + } + else + { + return 1; + } + } + + + static void AnswerDicomField(RestApiOutput& output, + DcmElement& element, + E_TransferSyntax transferSyntax) + { + // This element is nor a sequence, neither a pixel-data + std::string buffer; + buffer.resize(65536); + Uint32 length = element.getLength(transferSyntax); + Uint32 offset = 0; + + output.GetLowLevelOutput().SendOkHeader(CONTENT_TYPE_OCTET_STREAM, true, length, NULL); + + while (offset < length) + { + Uint32 nbytes; + if (length - offset < buffer.size()) + { + nbytes = length - offset; + } + else + { + nbytes = buffer.size(); + } + + OFCondition cond = element.getPartialValue(&buffer[0], offset, nbytes); + + if (cond.good()) + { + output.GetLowLevelOutput().Send(&buffer[0], nbytes); + offset += nbytes; + } + else + { + LOG(ERROR) << "Error while sending a DICOM field: " << cond.text(); + return; + } + } + + output.MarkLowLevelOutputDone(); + } + + + static bool AnswerPixelData(RestApiOutput& output, + DcmItem& dicom, + E_TransferSyntax transferSyntax, + const std::string* blockUri) + { + DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(), + DICOM_TAG_PIXEL_DATA.GetElement()); + + DcmElement *element = NULL; + if (!dicom.findAndGetElement(k, element).good() || + element == NULL) + { + return false; + } + + try + { + DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element); + if (blockUri == NULL) + { + // The user asks how many blocks are presents in this pixel data + unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax); + + Json::Value result(Json::arrayValue); + for (unsigned int i = 0; i < blocks; i++) + { + result.append(boost::lexical_cast<std::string>(i)); + } + + output.AnswerJson(result); + return true; + } + + + unsigned int block = boost::lexical_cast<unsigned int>(*blockUri); + + if (block < GetPixelDataBlockCount(pixelData, transferSyntax)) + { + DcmPixelSequence* pixelSequence = NULL; + if (pixelData.getEncapsulatedRepresentation + (transferSyntax, NULL, pixelSequence).good() && pixelSequence) + { + // This is the case for JPEG transfer syntaxes + if (block < pixelSequence->card()) + { + DcmPixelItem* pixelItem = NULL; + if (pixelSequence->getItem(pixelItem, block).good() && pixelItem) + { + if (pixelItem->getLength() == 0) + { + output.AnswerBuffer(NULL, 0, CONTENT_TYPE_OCTET_STREAM); + return true; + } + + Uint8* buffer = NULL; + if (pixelItem->getUint8Array(buffer).good() && buffer) + { + output.AnswerBuffer(buffer, pixelItem->getLength(), CONTENT_TYPE_OCTET_STREAM); + return true; + } + } + } + } + else + { + // This is the case for raw, uncompressed image buffers + assert(*blockUri == "0"); + AnswerDicomField(output, *element, transferSyntax); + } + } + } + catch (boost::bad_lexical_cast&) + { + // The URI entered by the user is not a number + } + catch (std::bad_cast&) + { + // This should never happen + } + + return false; + } + + + + static void SendPathValueForLeaf(RestApiOutput& output, + const std::string& tag, + DcmItem& dicom, + E_TransferSyntax transferSyntax) + { + DcmTagKey k; + ParseTagAndGroup(k, tag); + + DcmSequenceOfItems* sequence = NULL; + if (dicom.findAndGetSequence(k, sequence).good() && + sequence != NULL && + sequence->getVR() == EVR_SQ) + { + SendSequence(output, *sequence); + return; + } + + DcmElement* element = NULL; + if (dicom.findAndGetElement(k, element).good() && + element != NULL && + //element->getVR() != EVR_UNKNOWN && // This would forbid private tags + element->getVR() != EVR_SQ) + { + AnswerDicomField(output, *element, transferSyntax); + } + } + + void ParsedDicomFile::SendPathValue(RestApiOutput& output, + const UriComponents& uri) + { + DcmItem* dicom = file_->getDataset(); + E_TransferSyntax transferSyntax = file_->getDataset()->getOriginalXfer(); + + // Special case: Accessing the pixel data + if (uri.size() == 1 || + uri.size() == 2) + { + DcmTagKey tag; + ParseTagAndGroup(tag, uri[0]); + + if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() && + tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement()) + { + AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]); + return; + } + } + + // Go down in the tag hierarchy according to the URI + for (size_t pos = 0; pos < uri.size() / 2; pos++) + { + size_t index; + try + { + index = boost::lexical_cast<size_t>(uri[2 * pos + 1]); + } + catch (boost::bad_lexical_cast&) + { + return; + } + + DcmTagKey k; + DcmItem *child = NULL; + ParseTagAndGroup(k, uri[2 * pos]); + if (!dicom->findAndGetSequenceItem(k, child, index).good() || + child == NULL) + { + return; + } + + dicom = child; + } + + // We have reached the end of the URI + if (uri.size() % 2 == 0) + { + SendPathValueForDictionary(output, *dicom); + } + else + { + SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax); + } + } + + + + + + static DcmElement* CreateElementForTag(const DicomTag& tag) + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + + switch (key.getEVR()) + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + + /** + * TODO. + **/ + + case EVR_OB: // other byte + case EVR_OF: // other float + case EVR_OW: // other word + case EVR_AT: // attribute tag + throw OrthancException(ErrorCode_NotImplemented); + + case EVR_UN: // unknown value representation + throw OrthancException(ErrorCode_ParameterOutOfRange); + + + /** + * String types. + * http://support.dcmtk.org/docs/classDcmByteString.html + **/ + + case EVR_AS: // age string + return new DcmAgeString(key); + + case EVR_AE: // application entity title + return new DcmApplicationEntity(key); + + case EVR_CS: // code string + return new DcmCodeString(key); + + case EVR_DA: // date string + return new DcmDate(key); + + case EVR_DT: // date time string + return new DcmDateTime(key); + + case EVR_DS: // decimal string + return new DcmDecimalString(key); + + case EVR_IS: // integer string + return new DcmIntegerString(key); + + case EVR_TM: // time string + return new DcmTime(key); + + case EVR_UI: // unique identifier + return new DcmUniqueIdentifier(key); + + case EVR_ST: // short text + return new DcmShortText(key); + + case EVR_LO: // long string + return new DcmLongString(key); + + case EVR_LT: // long text + return new DcmLongText(key); + + case EVR_UT: // unlimited text + return new DcmUnlimitedText(key); + + case EVR_SH: // short string + return new DcmShortString(key); + + case EVR_PN: // person name + return new DcmPersonName(key); + + + /** + * Numerical types + **/ + + case EVR_SL: // signed long + return new DcmSignedLong(key); + + case EVR_SS: // signed short + return new DcmSignedShort(key); + + case EVR_UL: // unsigned long + return new DcmUnsignedLong(key); + + case EVR_US: // unsigned short + return new DcmUnsignedShort(key); + + case EVR_FL: // float single-precision + return new DcmFloatingPointSingle(key); + + case EVR_FD: // float double-precision + return new DcmFloatingPointDouble(key); + + + /** + * Sequence types, should never occur at this point. + **/ + + case EVR_SQ: // sequence of items + throw OrthancException(ErrorCode_ParameterOutOfRange); + + + /** + * Internal to DCMTK. + **/ + + case EVR_ox: // OB or OW depending on context + case EVR_xs: // SS or US depending on context + case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) + case EVR_na: // na="not applicable", for data which has no VR + case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor + case EVR_item: // used internally for items + case EVR_metainfo: // used internally for meta info datasets + case EVR_dataset: // used internally for datasets + case EVR_fileFormat: // used internally for DICOM files + case EVR_dicomDir: // used internally for DICOMDIR objects + case EVR_dirRecord: // used internally for DICOMDIR records + case EVR_pixelSQ: // used internally for pixel sequences in a compressed image + case EVR_pixelItem: // used internally for pixel items in a compressed image + case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) + case EVR_PixelData: // used internally for uncompressed pixeld data + case EVR_OverlayData: // used internally for overlay data + case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR + default: + break; + } + + throw OrthancException(ErrorCode_InternalError); + } + + + + static void FillElementWithString(DcmElement& element, + const DicomTag& tag, + const std::string& value) + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + bool ok = false; + + try + { + switch (key.getEVR()) + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + + /** + * TODO. + **/ + + case EVR_OB: // other byte + case EVR_OF: // other float + case EVR_OW: // other word + case EVR_AT: // attribute tag + throw OrthancException(ErrorCode_NotImplemented); + + case EVR_UN: // unknown value representation + throw OrthancException(ErrorCode_ParameterOutOfRange); + + + /** + * String types. + **/ + + 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 + case EVR_LO: // long string + case EVR_ST: // short text + case EVR_LT: // long text + case EVR_UT: // unlimited text + case EVR_PN: // person name + case EVR_UI: // unique identifier + { + ok = element.putString(value.c_str()).good(); + break; + } + + + /** + * Numerical types + **/ + + case EVR_SL: // signed long + { + ok = element.putSint32(boost::lexical_cast<Sint32>(value)).good(); + break; + } + + case EVR_SS: // signed short + { + ok = element.putSint16(boost::lexical_cast<Sint16>(value)).good(); + break; + } + + case EVR_UL: // unsigned long + { + ok = element.putUint32(boost::lexical_cast<Uint32>(value)).good(); + break; + } + + case EVR_US: // unsigned short + { + ok = element.putUint16(boost::lexical_cast<Uint16>(value)).good(); + break; + } + + case EVR_FL: // float single-precision + { + ok = element.putFloat32(boost::lexical_cast<float>(value)).good(); + break; + } + + case EVR_FD: // float double-precision + { + ok = element.putFloat64(boost::lexical_cast<double>(value)).good(); + break; + } + + + /** + * Sequence types, should never occur at this point. + **/ + + case EVR_SQ: // sequence of items + { + ok = false; + break; + } + + + /** + * Internal to DCMTK. + **/ + + case EVR_ox: // OB or OW depending on context + case EVR_xs: // SS or US depending on context + case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) + case EVR_na: // na="not applicable", for data which has no VR + case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor + case EVR_item: // used internally for items + case EVR_metainfo: // used internally for meta info datasets + case EVR_dataset: // used internally for datasets + case EVR_fileFormat: // used internally for DICOM files + case EVR_dicomDir: // used internally for DICOMDIR objects + case EVR_dirRecord: // used internally for DICOMDIR records + case EVR_pixelSQ: // used internally for pixel sequences in a compressed image + case EVR_pixelItem: // used internally for pixel items in a compressed image + case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) + case EVR_PixelData: // used internally for uncompressed pixeld data + case EVR_OverlayData: // used internally for overlay data + case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR + default: + break; + } + } + catch (boost::bad_lexical_cast&) + { + ok = false; + } + + if (!ok) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + + void ParsedDicomFile::Remove(const DicomTag& tag) + { + DcmTagKey key(tag.GetGroup(), tag.GetElement()); + DcmElement* element = file_->getDataset()->remove(key); + if (element != NULL) + { + delete element; + } + } + + + + void ParsedDicomFile::RemovePrivateTags() + { + typedef std::list<DcmElement*> 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 (!strcmp("PrivateCreator", tag.getTagName()) || // TODO - This may change with future versions of DCMTK + 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; + } + } + } + + + + void ParsedDicomFile::Insert(const DicomTag& tag, + const std::string& value) + { + std::auto_ptr<DcmElement> element(CreateElementForTag(tag)); + FillElementWithString(*element, tag, value); + + if (!file_->getDataset()->insert(element.release(), false, false).good()) + { + // This field already exists + throw OrthancException(ErrorCode_InternalError); + } + } + + + void ParsedDicomFile::Replace(const DicomTag& tag, + const std::string& value, + DicomReplaceMode mode) + { + DcmTagKey key(tag.GetGroup(), tag.GetElement()); + DcmElement* element = NULL; + + if (!file_->getDataset()->findAndGetElement(key, element).good() || + element == NULL) + { + // This field does not exist, act wrt. the specified "mode" + switch (mode) + { + case DicomReplaceMode_InsertIfAbsent: + Insert(tag, value); + break; + + case DicomReplaceMode_ThrowIfAbsent: + throw OrthancException(ErrorCode_InexistentItem); + + case DicomReplaceMode_IgnoreIfAbsent: + return; + } + } + else + { + FillElementWithString(*element, tag, value); + } + + + /** + * dcmodify will automatically correct 'Media Storage SOP Class + * UID' and 'Media Storage SOP Instance UID' in the metaheader, if + * you make changes to the related tags in the dataset ('SOP Class + * UID' and 'SOP Instance UID') via insert or modify mode + * options. You can disable this behaviour by using the -nmu + * option. + **/ + + if (tag == DICOM_TAG_SOP_CLASS_UID) + { + Replace(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, value, DicomReplaceMode_InsertIfAbsent); + } + + if (tag == DICOM_TAG_SOP_INSTANCE_UID) + { + Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, value, DicomReplaceMode_InsertIfAbsent); + } + } + + + void ParsedDicomFile::Answer(RestApiOutput& output) + { + std::string serialized; + if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, file_->getDataset())) + { + output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM); + } + } + + + + bool ParsedDicomFile::GetTagValue(std::string& value, + const DicomTag& tag) + { + DcmTagKey k(tag.GetGroup(), tag.GetElement()); + DcmDataset& dataset = *file_->getDataset(); + DcmElement* element = NULL; + if (!dataset.findAndGetElement(k, element).good() || + element == NULL) + { + return false; + } + + std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element)); + + if (v.get() == NULL) + { + value = ""; + } + else + { + value = v->AsString(); + } + + return true; + } + + + + DicomInstanceHasher ParsedDicomFile::GetHasher() + { + std::string patientId, studyUid, seriesUid, instanceUid; + + if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID) || + !GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) || + !GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) || + !GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid); + } + + + static void StoreElement(Json::Value& target, + DcmElement& element, + unsigned int maxStringLength); + + static void StoreItem(Json::Value& target, + DcmItem& item, + unsigned int maxStringLength) + { + target = Json::Value(Json::objectValue); + + for (unsigned long i = 0; i < item.card(); i++) + { + DcmElement* element = item.getElement(i); + StoreElement(target, *element, maxStringLength); + } + } + + + static void StoreElement(Json::Value& target, + DcmElement& element, + unsigned int maxStringLength) + { + assert(target.type() == Json::objectValue); + + DicomTag tag(FromDcmtkBridge::GetTag(element)); + const std::string formattedTag = tag.Format(); + +#if 0 + const std::string tagName = FromDcmtkBridge::GetName(tag); +#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()); +#endif + + if (element.isLeaf()) + { + Json::Value value(Json::objectValue); + value["Name"] = tagName; + + if (tagbis.getPrivateCreator() != NULL) + { + value["PrivateCreator"] = tagbis.getPrivateCreator(); + } + + std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element)); + if (v->IsNull()) + { + value["Type"] = "Null"; + value["Value"] = Json::nullValue; + } + else + { + std::string s = v->AsString(); + if (maxStringLength == 0 || + s.size() <= maxStringLength) + { + value["Type"] = "String"; + value["Value"] = s; + } + else + { + value["Type"] = "TooLong"; + value["Value"] = Json::nullValue; + } + } + + target[formattedTag] = value; + } + else + { + Json::Value children(Json::arrayValue); + + // "All subclasses of DcmElement except for DcmSequenceOfItems + // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset + // etc. are not." The following cast is thus OK. + DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element); + + for (unsigned long i = 0; i < sequence.card(); i++) + { + DcmItem* child = sequence.getItem(i); + Json::Value& v = children.append(Json::objectValue); + StoreItem(v, *child, maxStringLength); + } + + target[formattedTag]["Name"] = tagName; + target[formattedTag]["Type"] = "Sequence"; + target[formattedTag]["Value"] = children; + } + } + + + template <typename T> + static void ExtractPngImageTruncate(std::string& result, + DicomIntegerPixelAccessor& accessor, + PixelFormat format) + { + assert(accessor.GetChannelCount() == 1); + + PngWriter w; + + std::vector<T> image(accessor.GetWidth() * accessor.GetHeight(), 0); + T* pixel = &image[0]; + for (unsigned int y = 0; y < accessor.GetHeight(); y++) + { + for (unsigned int x = 0; x < accessor.GetWidth(); x++, pixel++) + { + int32_t v = accessor.GetValue(x, y); + if (v < static_cast<int32_t>(std::numeric_limits<T>::min())) + *pixel = std::numeric_limits<T>::min(); + else if (v > static_cast<int32_t>(std::numeric_limits<T>::max())) + *pixel = std::numeric_limits<T>::max(); + else + *pixel = static_cast<T>(v); + } + } + + w.WriteToMemory(result, accessor.GetWidth(), accessor.GetHeight(), + accessor.GetWidth() * sizeof(T), format, &image[0]); + } + + + void ParsedDicomFile::SaveToMemoryBuffer(std::string& buffer) + { + FromDcmtkBridge::SaveToMemoryBuffer(buffer, file_->getDataset()); + } + + + void ParsedDicomFile::SaveToFile(const std::string& path) + { + // TODO Avoid using a temporary memory buffer, write directly on disk + std::string content; + SaveToMemoryBuffer(content); + Toolbox::WriteFile(content, path); + } + + + ParsedDicomFile::ParsedDicomFile() + { + file_.reset(new DcmFileFormat); + Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient)); + Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study)); + Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series)); + Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ParsedDicomFile.h Mon May 05 18:55:10 2014 +0200 @@ -0,0 +1,112 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Core/DicomFormat/DicomInstanceHasher.h" +#include "../Core/RestApi/RestApiOutput.h" +#include "../Core/Toolbox.h" +#include "ServerEnumerations.h" + +#include <memory> +#include <dcmtk/dcmdata/dcfilefo.h> + +namespace Orthanc +{ + class ParsedDicomFile : public IDynamicObject + { + private: + // TODO Move this as pimpl + std::auto_ptr<DcmFileFormat> file_; + + ParsedDicomFile(DcmFileFormat& other) : + file_(dynamic_cast<DcmFileFormat*>(other.clone())) + { + } + + void Setup(const char* content, + size_t size); + + public: + ParsedDicomFile(); // Create a minimal DICOM instance + + 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() + { + return *file_; + } + + ParsedDicomFile* Clone() + { + return new ParsedDicomFile(*file_); + } + + void SendPathValue(RestApiOutput& output, + const UriComponents& uri); + + void Answer(RestApiOutput& output); + + void Remove(const DicomTag& tag); + + void Insert(const DicomTag& tag, + const std::string& value); + + void Replace(const DicomTag& tag, + const std::string& value, + DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent); + + void RemovePrivateTags(); + + bool GetTagValue(std::string& value, + const DicomTag& tag); + + DicomInstanceHasher GetHasher(); + + void SaveToMemoryBuffer(std::string& buffer); + + void SaveToFile(const std::string& path); + }; + +}
--- a/OrthancServer/ServerContext.h Mon May 05 18:43:34 2014 +0200 +++ b/OrthancServer/ServerContext.h Mon May 05 18:55:10 2014 +0200 @@ -39,6 +39,7 @@ #include "../Core/Lua/LuaContext.h" #include "ServerIndex.h" #include "FromDcmtkBridge.h" +#include "ParsedDicomFile.h" #include "DicomProtocol/ReusableDicomUserConnection.h" namespace Orthanc