Mercurial > hg > orthanc-stone
diff Framework/Deprecated/Loaders/OrthancMultiframeVolumeLoader.cpp @ 1225:16738485e457 broker
deprecating DicomStructureSetLoader, OrthancMultiframeVolumeLoader and OrthancSeriesVolumeProgressiveLoader
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Sun, 08 Dec 2019 11:45:09 +0100 |
parents | Framework/Loaders/OrthancMultiframeVolumeLoader.cpp@8e3763d1736a |
children | 0ca50d275b9a |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Loaders/OrthancMultiframeVolumeLoader.cpp Sun Dec 08 11:45:09 2019 +0100 @@ -0,0 +1,383 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancMultiframeVolumeLoader.h" + +#include <Core/Endianness.h> +#include <Core/Toolbox.h> + +namespace Deprecated +{ + class OrthancMultiframeVolumeLoader::LoadRTDoseGeometry : public LoaderStateMachine::State + { + private: + std::auto_ptr<Orthanc::DicomMap> dicom_; + + public: + LoadRTDoseGeometry(OrthancMultiframeVolumeLoader& that, + Orthanc::DicomMap* dicom) : + State(that), + dicom_(dicom) + { + if (dicom == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + } + + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) + { + // Complete the DICOM tags with just-received "Grid Frame Offset Vector" + std::string s = Orthanc::Toolbox::StripSpaces(message.GetAnswer()); + dicom_->SetValue(Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, s, false); + + GetLoader<OrthancMultiframeVolumeLoader>().SetGeometry(*dicom_); + } + }; + + + static std::string GetSopClassUid(const Orthanc::DicomMap& dicom) + { + std::string s; + if (!dicom.LookupStringValue(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "DICOM file without SOP class UID"); + } + else + { + return s; + } + } + + + class OrthancMultiframeVolumeLoader::LoadGeometry : public State + { + public: + LoadGeometry(OrthancMultiframeVolumeLoader& that) : + State(that) + { + } + + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) + { + OrthancMultiframeVolumeLoader& loader = GetLoader<OrthancMultiframeVolumeLoader>(); + + Json::Value body; + message.ParseJsonBody(body); + + if (body.type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + std::auto_ptr<Orthanc::DicomMap> dicom(new Orthanc::DicomMap); + dicom->FromDicomAsJson(body); + + if (OrthancStone::StringToSopClassUid(GetSopClassUid(*dicom)) == OrthancStone::SopClassUid_RTDose) + { + // Download the "Grid Frame Offset Vector" DICOM tag, that is + // mandatory for RT-DOSE, but is too long to be returned by default + + std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand); + command->SetUri("/instances/" + loader.GetInstanceId() + "/content/" + + Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format()); + command->AcquirePayload(new LoadRTDoseGeometry(loader, dicom.release())); + + Schedule(command.release()); + } + else + { + loader.SetGeometry(*dicom); + } + } + }; + + class OrthancMultiframeVolumeLoader::LoadTransferSyntax : public State + { + public: + LoadTransferSyntax(OrthancMultiframeVolumeLoader& that) : + State(that) + { + } + + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) + { + GetLoader<OrthancMultiframeVolumeLoader>().SetTransferSyntax(message.GetAnswer()); + } + }; + + class OrthancMultiframeVolumeLoader::LoadUncompressedPixelData : public State + { + public: + LoadUncompressedPixelData(OrthancMultiframeVolumeLoader& that) : + State(that) + { + } + + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) + { + GetLoader<OrthancMultiframeVolumeLoader>().SetUncompressedPixelData(message.GetAnswer()); + } + }; + + const std::string& OrthancMultiframeVolumeLoader::GetInstanceId() const + { + if (IsActive()) + { + return instanceId_; + } + else + { + LOG(ERROR) << "OrthancMultiframeVolumeLoader::GetInstanceId(): (!IsActive())"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + void OrthancMultiframeVolumeLoader::ScheduleFrameDownloads() + { + if (transferSyntaxUid_.empty() || + !volume_->HasGeometry()) + { + return; + } + /* + 1.2.840.10008.1.2 Implicit VR Endian: Default Transfer Syntax for DICOM + 1.2.840.10008.1.2.1 Explicit VR Little Endian + 1.2.840.10008.1.2.2 Explicit VR Big Endian + + See https://www.dicomlibrary.com/dicom/transfer-syntax/ + */ + if (transferSyntaxUid_ == "1.2.840.10008.1.2" || + transferSyntaxUid_ == "1.2.840.10008.1.2.1" || + transferSyntaxUid_ == "1.2.840.10008.1.2.2") + { + std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + command->SetUri("/instances/" + instanceId_ + "/content/" + + Orthanc::DICOM_TAG_PIXEL_DATA.Format() + "/0"); + command->AcquirePayload(new LoadUncompressedPixelData(*this)); + Schedule(command.release()); + } + else + { + throw Orthanc::OrthancException( + Orthanc::ErrorCode_NotImplemented, + "No support for multiframe instances with transfer syntax: " + transferSyntaxUid_); + } + } + + void OrthancMultiframeVolumeLoader::SetTransferSyntax(const std::string& transferSyntax) + { + transferSyntaxUid_ = Orthanc::Toolbox::StripSpaces(transferSyntax); + ScheduleFrameDownloads(); + } + + void OrthancMultiframeVolumeLoader::SetGeometry(const Orthanc::DicomMap& dicom) + { + OrthancStone::DicomInstanceParameters parameters(dicom); + volume_->SetDicomParameters(parameters); + + Orthanc::PixelFormat format; + if (!parameters.GetImageInformation().ExtractPixelFormat(format, true)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + double spacingZ; + switch (parameters.GetSopClassUid()) + { + case OrthancStone::SopClassUid_RTDose: + spacingZ = parameters.GetThickness(); + break; + + default: + throw Orthanc::OrthancException( + Orthanc::ErrorCode_NotImplemented, + "No support for multiframe instances with SOP class UID: " + GetSopClassUid(dicom)); + } + + const unsigned int width = parameters.GetImageInformation().GetWidth(); + const unsigned int height = parameters.GetImageInformation().GetHeight(); + const unsigned int depth = parameters.GetImageInformation().GetNumberOfFrames(); + + { + OrthancStone::VolumeImageGeometry geometry; + geometry.SetSizeInVoxels(width, height, depth); + geometry.SetAxialGeometry(parameters.GetGeometry()); + geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(), + parameters.GetPixelSpacingY(), spacingZ); + volume_->Initialize(geometry, format, true /* Do compute range */); + } + + volume_->GetPixelData().Clear(); + + ScheduleFrameDownloads(); + + + + BroadcastMessage(OrthancStone::DicomVolumeImage::GeometryReadyMessage(*volume_)); + } + + + ORTHANC_FORCE_INLINE + static void CopyPixel(uint32_t& target, const void* source) + { + // TODO - check alignement? + target = le32toh(*reinterpret_cast<const uint32_t*>(source)); + } + + ORTHANC_FORCE_INLINE + static void CopyPixel(uint16_t& target, const void* source) + { + // TODO - check alignement? + target = le16toh(*reinterpret_cast<const uint16_t*>(source)); + } + + ORTHANC_FORCE_INLINE + static void CopyPixel(int16_t& target, const void* source) + { + // byte swapping is the same for unsigned and signed integers + // (the sign bit is always stored with the MSByte) + uint16_t* targetUp = reinterpret_cast<uint16_t*>(&target); + CopyPixel(*targetUp, source); + } + + template <typename T> + void OrthancMultiframeVolumeLoader::CopyPixelData(const std::string& pixelData) + { + OrthancStone::ImageBuffer3D& target = volume_->GetPixelData(); + + const unsigned int bpp = target.GetBytesPerPixel(); + const unsigned int width = target.GetWidth(); + const unsigned int height = target.GetHeight(); + const unsigned int depth = target.GetDepth(); + + if (pixelData.size() != bpp * width * height * depth) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "The pixel data has not the proper size"); + } + + if (pixelData.empty()) + { + return; + } + + const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData.c_str()); + + for (unsigned int z = 0; z < depth; z++) + { + OrthancStone::ImageBuffer3D::SliceWriter writer(target, OrthancStone::VolumeProjection_Axial, z); + + assert (writer.GetAccessor().GetWidth() == width && + writer.GetAccessor().GetHeight() == height); + + for (unsigned int y = 0; y < height; y++) + { + assert(sizeof(T) == Orthanc::GetBytesPerPixel(target.GetFormat())); + + T* target = reinterpret_cast<T*>(writer.GetAccessor().GetRow(y)); + + for (unsigned int x = 0; x < width; x++) + { + CopyPixel(*target, source); + target ++; + source += bpp; + } + } + } + } + + void OrthancMultiframeVolumeLoader::SetUncompressedPixelData(const std::string& pixelData) + { + switch (volume_->GetPixelData().GetFormat()) + { + case Orthanc::PixelFormat_Grayscale32: + CopyPixelData<uint32_t>(pixelData); + break; + case Orthanc::PixelFormat_Grayscale16: + CopyPixelData<uint16_t>(pixelData); + break; + case Orthanc::PixelFormat_SignedGrayscale16: + CopyPixelData<int16_t>(pixelData); + break; + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + volume_->IncrementRevision(); + + pixelDataLoaded_ = true; + BroadcastMessage(OrthancStone::DicomVolumeImage::ContentUpdatedMessage(*volume_)); + } + + bool OrthancMultiframeVolumeLoader::HasGeometry() const + { + return volume_->HasGeometry(); + } + + const OrthancStone::VolumeImageGeometry& OrthancMultiframeVolumeLoader::GetImageGeometry() const + { + return volume_->GetGeometry(); + } + + OrthancMultiframeVolumeLoader::OrthancMultiframeVolumeLoader(boost::shared_ptr<OrthancStone::DicomVolumeImage> volume, + OrthancStone::IOracle& oracle, + OrthancStone::IObservable& oracleObservable) : + LoaderStateMachine(oracle, oracleObservable), + volume_(volume), + pixelDataLoaded_(false) + { + if (volume.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + OrthancMultiframeVolumeLoader::~OrthancMultiframeVolumeLoader() + { + LOG(TRACE) << "OrthancMultiframeVolumeLoader::~OrthancMultiframeVolumeLoader()"; + } + + void OrthancMultiframeVolumeLoader::LoadInstance(const std::string& instanceId) + { + Start(); + + instanceId_ = instanceId; + + { + std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + command->SetUri("/instances/" + instanceId + "/tags"); + command->AcquirePayload(new LoadGeometry(*this)); + Schedule(command.release()); + } + + { + std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand); + command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax"); + command->AcquirePayload(new LoadTransferSyntax(*this)); + Schedule(command.release()); + } + } +}