Mercurial > hg > orthanc
diff Core/DicomParsing/DicomDirWriter.cpp @ 2382:7284093111b0
big reorganization to cleanly separate framework vs. server
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 29 Aug 2017 21:17:35 +0200 |
parents | OrthancServer/DicomDirWriter.cpp@a3a65de1840f |
children | d0fe5ec7eb05 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/DicomDirWriter.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,510 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, 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: DCMTK 3.6.0 + Module: http://dicom.offis.de/dcmtk.php.en + +Copyright (C) 1994-2011, OFFIS e.V. +All rights reserved. + +This software and supporting documentation were developed by + + OFFIS e.V. + R&D Division Health + Escherweg 2 + 26121 Oldenburg, Germany + +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 the name of OFFIS nor the names of its contributors 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 COPYRIGHT +HOLDER 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. + +=========================================================================*/ + + + +/*** + + Validation: + + # sudo apt-get install dicom3tools + # dciodvfy DICOMDIR 2>&1 | less + # dcentvfy DICOMDIR 2>&1 | less + + http://www.dclunie.com/dicom3tools/dciodvfy.html + + DICOMDIR viewer working with Wine under Linux: + http://www.microdicom.com/ + + ***/ + + +#include "../PrecompiledHeaders.h" +#include "DicomDirWriter.h" + +#include "FromDcmtkBridge.h" +#include "ToDcmtkBridge.h" + +#include "../Logging.h" +#include "../OrthancException.h" +#include "../TemporaryFile.h" +#include "../Toolbox.h" +#include "../SystemToolbox.h" + +#include <dcmtk/dcmdata/dcdicdir.h> +#include <dcmtk/dcmdata/dcmetinf.h> +#include <dcmtk/dcmdata/dcdeftag.h> +#include <dcmtk/dcmdata/dcuid.h> +#include <dcmtk/dcmdata/dcddirif.h> +#include <dcmtk/dcmdata/dcvrui.h> +#include <dcmtk/dcmdata/dcsequen.h> +#include <dcmtk/dcmdata/dcostrmf.h> +#include "dcmtk/dcmdata/dcvrda.h" /* for class DcmDate */ +#include "dcmtk/dcmdata/dcvrtm.h" /* for class DcmTime */ + +#include <memory> + +namespace Orthanc +{ + class DicomDirWriter::PImpl + { + private: + std::string fileSetId_; + TemporaryFile file_; + std::auto_ptr<DcmDicomDir> dir_; + + typedef std::pair<ResourceType, std::string> IndexKey; + typedef std::map<IndexKey, DcmDirectoryRecord* > Index; + Index index_; + + + DcmDicomDir& GetDicomDir() + { + if (dir_.get() == NULL) + { + dir_.reset(new DcmDicomDir(file_.GetPath().c_str(), + fileSetId_.c_str())); + //SetTagValue(dir_->getRootRecord(), DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(Encoding_Utf8)); + } + + return *dir_; + } + + + DcmDirectoryRecord& GetRoot() + { + return GetDicomDir().getRootRecord(); + } + + + static bool GetUtf8TagValue(std::string& result, + DcmItem& source, + Encoding encoding, + const DcmTagKey& key) + { + DcmElement* element = NULL; + + if (source.findAndGetElement(key, element).good()) + { + char* s = NULL; + if (element->isLeaf() && + element->getString(s).good() && + s != NULL) + { + result = Toolbox::ConvertToUtf8(s, encoding); + return true; + } + } + + result.clear(); + return false; + } + + + static void SetTagValue(DcmDirectoryRecord& target, + const DcmTagKey& key, + const std::string& valueUtf8) + { + std::string s = Toolbox::ConvertFromUtf8(valueUtf8, Encoding_Ascii); + + if (!target.putAndInsertString(key, s.c_str()).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + + + static bool CopyString(DcmDirectoryRecord& target, + DcmDataset& source, + Encoding encoding, + const DcmTagKey& key, + bool optional, + bool copyEmpty) + { + if (optional && + !source.tagExistsWithValue(key) && + !(copyEmpty && source.tagExists(key))) + { + return false; + } + + std::string value; + bool found = GetUtf8TagValue(value, source, encoding, key); + + SetTagValue(target, key, value); + return found; + } + + + static void CopyStringType1(DcmDirectoryRecord& target, + DcmDataset& source, + Encoding encoding, + const DcmTagKey& key) + { + CopyString(target, source, encoding, key, false, false); + } + + static void CopyStringType1C(DcmDirectoryRecord& target, + DcmDataset& source, + Encoding encoding, + const DcmTagKey& key) + { + CopyString(target, source, encoding, key, true, false); + } + + static void CopyStringType2(DcmDirectoryRecord& target, + DcmDataset& source, + Encoding encoding, + const DcmTagKey& key) + { + CopyString(target, source, encoding, key, false, true); + } + + + public: + PImpl() : fileSetId_("ORTHANC_MEDIA") + { + } + + void FillPatient(DcmDirectoryRecord& record, + DcmDataset& dicom, + Encoding encoding) + { + // cf. "DicomDirInterface::buildPatientRecord()" + + CopyStringType1C(record, dicom, encoding, DCM_PatientID); + CopyStringType2(record, dicom, encoding, DCM_PatientName); + } + + void FillStudy(DcmDirectoryRecord& record, + DcmDataset& dicom, + Encoding encoding) + { + // cf. "DicomDirInterface::buildStudyRecord()" + + std::string nowDate, nowTime; + SystemToolbox::GetNowDicom(nowDate, nowTime); + + std::string studyDate; + if (!GetUtf8TagValue(studyDate, dicom, encoding, DCM_StudyDate) && + !GetUtf8TagValue(studyDate, dicom, encoding, DCM_SeriesDate) && + !GetUtf8TagValue(studyDate, dicom, encoding, DCM_AcquisitionDate) && + !GetUtf8TagValue(studyDate, dicom, encoding, DCM_ContentDate)) + { + studyDate = nowDate; + } + + std::string studyTime; + if (!GetUtf8TagValue(studyTime, dicom, encoding, DCM_StudyTime) && + !GetUtf8TagValue(studyTime, dicom, encoding, DCM_SeriesTime) && + !GetUtf8TagValue(studyTime, dicom, encoding, DCM_AcquisitionTime) && + !GetUtf8TagValue(studyTime, dicom, encoding, DCM_ContentTime)) + { + studyTime = nowTime; + } + + /* copy attribute values from dataset to study record */ + SetTagValue(record, DCM_StudyDate, studyDate); + SetTagValue(record, DCM_StudyTime, studyTime); + CopyStringType2(record, dicom, encoding, DCM_StudyDescription); + CopyStringType1(record, dicom, encoding, DCM_StudyInstanceUID); + /* use type 1C instead of 1 in order to avoid unwanted overwriting */ + CopyStringType1C(record, dicom, encoding, DCM_StudyID); + CopyStringType2(record, dicom, encoding, DCM_AccessionNumber); + } + + void FillSeries(DcmDirectoryRecord& record, + DcmDataset& dicom, + Encoding encoding) + { + // cf. "DicomDirInterface::buildSeriesRecord()" + + /* copy attribute values from dataset to series record */ + CopyStringType1(record, dicom, encoding, DCM_Modality); + CopyStringType1(record, dicom, encoding, DCM_SeriesInstanceUID); + /* use type 1C instead of 1 in order to avoid unwanted overwriting */ + CopyStringType1C(record, dicom, encoding, DCM_SeriesNumber); + } + + void FillInstance(DcmDirectoryRecord& record, + DcmDataset& dicom, + Encoding encoding, + DcmMetaInfo& metaInfo, + const char* path) + { + // cf. "DicomDirInterface::buildImageRecord()" + + /* copy attribute values from dataset to image record */ + CopyStringType1(record, dicom, encoding, DCM_InstanceNumber); + //CopyElementType1C(record, dicom, encoding, DCM_ImageType); + + // REMOVED since 0.9.7: copyElementType1C(dicom, DCM_ReferencedImageSequence, record); + + std::string sopClassUid, sopInstanceUid, transferSyntaxUid; + if (!GetUtf8TagValue(sopClassUid, dicom, encoding, DCM_SOPClassUID) || + !GetUtf8TagValue(sopInstanceUid, dicom, encoding, DCM_SOPInstanceUID) || + !GetUtf8TagValue(transferSyntaxUid, metaInfo, encoding, DCM_TransferSyntaxUID)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + SetTagValue(record, DCM_ReferencedFileID, path); + SetTagValue(record, DCM_ReferencedSOPClassUIDInFile, sopClassUid); + SetTagValue(record, DCM_ReferencedSOPInstanceUIDInFile, sopInstanceUid); + SetTagValue(record, DCM_ReferencedTransferSyntaxUIDInFile, transferSyntaxUid); + } + + + + bool CreateResource(DcmDirectoryRecord*& target, + ResourceType level, + ParsedDicomFile& dicom, + const char* filename, + const char* path) + { + DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); + Encoding encoding = dicom.GetEncoding(); + + bool found; + std::string id; + E_DirRecType type; + + switch (level) + { + case ResourceType_Patient: + found = GetUtf8TagValue(id, dataset, encoding, DCM_PatientID); + type = ERT_Patient; + break; + + case ResourceType_Study: + found = GetUtf8TagValue(id, dataset, encoding, DCM_StudyInstanceUID); + type = ERT_Study; + break; + + case ResourceType_Series: + found = GetUtf8TagValue(id, dataset, encoding, DCM_SeriesInstanceUID); + type = ERT_Series; + break; + + case ResourceType_Instance: + found = GetUtf8TagValue(id, dataset, encoding, DCM_SOPInstanceUID); + type = ERT_Image; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + if (!found) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + IndexKey key = std::make_pair(level, std::string(id.c_str())); + Index::iterator it = index_.find(key); + + if (it != index_.end()) + { + target = it->second; + return false; // Already existing + } + + std::auto_ptr<DcmDirectoryRecord> record(new DcmDirectoryRecord(type, NULL, filename)); + + switch (level) + { + case ResourceType_Patient: + FillPatient(*record, dataset, encoding); + break; + + case ResourceType_Study: + FillStudy(*record, dataset, encoding); + break; + + case ResourceType_Series: + FillSeries(*record, dataset, encoding); + break; + + case ResourceType_Instance: + FillInstance(*record, dataset, encoding, *dicom.GetDcmtkObject().getMetaInfo(), path); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + CopyStringType1C(*record, dataset, encoding, DCM_SpecificCharacterSet); + + target = record.get(); + GetRoot().insertSub(record.release()); + index_[key] = target; + + return true; // Newly created + } + + void Read(std::string& s) + { + if (!GetDicomDir().write(DICOMDIR_DEFAULT_TRANSFERSYNTAX, + EET_UndefinedLength /*encodingType*/, + EGL_withoutGL /*groupLength*/).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + + file_.Read(s); + } + + void SetFileSetId(const std::string& id) + { + dir_.reset(NULL); + fileSetId_ = id; + } + }; + + + DicomDirWriter::DicomDirWriter() : pimpl_(new PImpl) + { + } + + DicomDirWriter::~DicomDirWriter() + { + if (pimpl_) + { + delete pimpl_; + } + } + + void DicomDirWriter::SetFileSetId(const std::string& id) + { + pimpl_->SetFileSetId(id); + } + + void DicomDirWriter::Add(const std::string& directory, + const std::string& filename, + ParsedDicomFile& dicom) + { + std::string path; + if (directory.empty()) + { + path = filename; + } + else + { + if (directory[directory.length() - 1] == '/' || + directory[directory.length() - 1] == '\\') + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + path = directory + '\\' + filename; + } + + DcmDirectoryRecord* instance; + bool isNewInstance = pimpl_->CreateResource(instance, ResourceType_Instance, dicom, filename.c_str(), path.c_str()); + if (isNewInstance) + { + DcmDirectoryRecord* series; + bool isNewSeries = pimpl_->CreateResource(series, ResourceType_Series, dicom, filename.c_str(), NULL); + series->insertSub(instance); + + if (isNewSeries) + { + DcmDirectoryRecord* study; + bool isNewStudy = pimpl_->CreateResource(study, ResourceType_Study, dicom, filename.c_str(), NULL); + study->insertSub(series); + + if (isNewStudy) + { + DcmDirectoryRecord* patient; + pimpl_->CreateResource(patient, ResourceType_Patient, dicom, filename.c_str(), NULL); + patient->insertSub(study); + } + } + } + } + + void DicomDirWriter::Encode(std::string& target) + { + pimpl_->Read(target); + } +}