comparison 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
comparison
equal deleted inserted replaced
1224:37bc7f115f81 1225:16738485e457
1 /**
2 * Stone of Orthanc
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2019 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Affero General Public License
9 * as published by the Free Software Foundation, either version 3 of
10 * the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/
20
21
22 #include "OrthancMultiframeVolumeLoader.h"
23
24 #include <Core/Endianness.h>
25 #include <Core/Toolbox.h>
26
27 namespace Deprecated
28 {
29 class OrthancMultiframeVolumeLoader::LoadRTDoseGeometry : public LoaderStateMachine::State
30 {
31 private:
32 std::auto_ptr<Orthanc::DicomMap> dicom_;
33
34 public:
35 LoadRTDoseGeometry(OrthancMultiframeVolumeLoader& that,
36 Orthanc::DicomMap* dicom) :
37 State(that),
38 dicom_(dicom)
39 {
40 if (dicom == NULL)
41 {
42 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
43 }
44
45 }
46
47 virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
48 {
49 // Complete the DICOM tags with just-received "Grid Frame Offset Vector"
50 std::string s = Orthanc::Toolbox::StripSpaces(message.GetAnswer());
51 dicom_->SetValue(Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, s, false);
52
53 GetLoader<OrthancMultiframeVolumeLoader>().SetGeometry(*dicom_);
54 }
55 };
56
57
58 static std::string GetSopClassUid(const Orthanc::DicomMap& dicom)
59 {
60 std::string s;
61 if (!dicom.LookupStringValue(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
62 {
63 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
64 "DICOM file without SOP class UID");
65 }
66 else
67 {
68 return s;
69 }
70 }
71
72
73 class OrthancMultiframeVolumeLoader::LoadGeometry : public State
74 {
75 public:
76 LoadGeometry(OrthancMultiframeVolumeLoader& that) :
77 State(that)
78 {
79 }
80
81 virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
82 {
83 OrthancMultiframeVolumeLoader& loader = GetLoader<OrthancMultiframeVolumeLoader>();
84
85 Json::Value body;
86 message.ParseJsonBody(body);
87
88 if (body.type() != Json::objectValue)
89 {
90 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
91 }
92
93 std::auto_ptr<Orthanc::DicomMap> dicom(new Orthanc::DicomMap);
94 dicom->FromDicomAsJson(body);
95
96 if (OrthancStone::StringToSopClassUid(GetSopClassUid(*dicom)) == OrthancStone::SopClassUid_RTDose)
97 {
98 // Download the "Grid Frame Offset Vector" DICOM tag, that is
99 // mandatory for RT-DOSE, but is too long to be returned by default
100
101 std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
102 command->SetUri("/instances/" + loader.GetInstanceId() + "/content/" +
103 Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format());
104 command->AcquirePayload(new LoadRTDoseGeometry(loader, dicom.release()));
105
106 Schedule(command.release());
107 }
108 else
109 {
110 loader.SetGeometry(*dicom);
111 }
112 }
113 };
114
115 class OrthancMultiframeVolumeLoader::LoadTransferSyntax : public State
116 {
117 public:
118 LoadTransferSyntax(OrthancMultiframeVolumeLoader& that) :
119 State(that)
120 {
121 }
122
123 virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
124 {
125 GetLoader<OrthancMultiframeVolumeLoader>().SetTransferSyntax(message.GetAnswer());
126 }
127 };
128
129 class OrthancMultiframeVolumeLoader::LoadUncompressedPixelData : public State
130 {
131 public:
132 LoadUncompressedPixelData(OrthancMultiframeVolumeLoader& that) :
133 State(that)
134 {
135 }
136
137 virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
138 {
139 GetLoader<OrthancMultiframeVolumeLoader>().SetUncompressedPixelData(message.GetAnswer());
140 }
141 };
142
143 const std::string& OrthancMultiframeVolumeLoader::GetInstanceId() const
144 {
145 if (IsActive())
146 {
147 return instanceId_;
148 }
149 else
150 {
151 LOG(ERROR) << "OrthancMultiframeVolumeLoader::GetInstanceId(): (!IsActive())";
152 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
153 }
154 }
155
156 void OrthancMultiframeVolumeLoader::ScheduleFrameDownloads()
157 {
158 if (transferSyntaxUid_.empty() ||
159 !volume_->HasGeometry())
160 {
161 return;
162 }
163 /*
164 1.2.840.10008.1.2 Implicit VR Endian: Default Transfer Syntax for DICOM
165 1.2.840.10008.1.2.1 Explicit VR Little Endian
166 1.2.840.10008.1.2.2 Explicit VR Big Endian
167
168 See https://www.dicomlibrary.com/dicom/transfer-syntax/
169 */
170 if (transferSyntaxUid_ == "1.2.840.10008.1.2" ||
171 transferSyntaxUid_ == "1.2.840.10008.1.2.1" ||
172 transferSyntaxUid_ == "1.2.840.10008.1.2.2")
173 {
174 std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
175 command->SetHttpHeader("Accept-Encoding", "gzip");
176 command->SetUri("/instances/" + instanceId_ + "/content/" +
177 Orthanc::DICOM_TAG_PIXEL_DATA.Format() + "/0");
178 command->AcquirePayload(new LoadUncompressedPixelData(*this));
179 Schedule(command.release());
180 }
181 else
182 {
183 throw Orthanc::OrthancException(
184 Orthanc::ErrorCode_NotImplemented,
185 "No support for multiframe instances with transfer syntax: " + transferSyntaxUid_);
186 }
187 }
188
189 void OrthancMultiframeVolumeLoader::SetTransferSyntax(const std::string& transferSyntax)
190 {
191 transferSyntaxUid_ = Orthanc::Toolbox::StripSpaces(transferSyntax);
192 ScheduleFrameDownloads();
193 }
194
195 void OrthancMultiframeVolumeLoader::SetGeometry(const Orthanc::DicomMap& dicom)
196 {
197 OrthancStone::DicomInstanceParameters parameters(dicom);
198 volume_->SetDicomParameters(parameters);
199
200 Orthanc::PixelFormat format;
201 if (!parameters.GetImageInformation().ExtractPixelFormat(format, true))
202 {
203 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
204 }
205
206 double spacingZ;
207 switch (parameters.GetSopClassUid())
208 {
209 case OrthancStone::SopClassUid_RTDose:
210 spacingZ = parameters.GetThickness();
211 break;
212
213 default:
214 throw Orthanc::OrthancException(
215 Orthanc::ErrorCode_NotImplemented,
216 "No support for multiframe instances with SOP class UID: " + GetSopClassUid(dicom));
217 }
218
219 const unsigned int width = parameters.GetImageInformation().GetWidth();
220 const unsigned int height = parameters.GetImageInformation().GetHeight();
221 const unsigned int depth = parameters.GetImageInformation().GetNumberOfFrames();
222
223 {
224 OrthancStone::VolumeImageGeometry geometry;
225 geometry.SetSizeInVoxels(width, height, depth);
226 geometry.SetAxialGeometry(parameters.GetGeometry());
227 geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(),
228 parameters.GetPixelSpacingY(), spacingZ);
229 volume_->Initialize(geometry, format, true /* Do compute range */);
230 }
231
232 volume_->GetPixelData().Clear();
233
234 ScheduleFrameDownloads();
235
236
237
238 BroadcastMessage(OrthancStone::DicomVolumeImage::GeometryReadyMessage(*volume_));
239 }
240
241
242 ORTHANC_FORCE_INLINE
243 static void CopyPixel(uint32_t& target, const void* source)
244 {
245 // TODO - check alignement?
246 target = le32toh(*reinterpret_cast<const uint32_t*>(source));
247 }
248
249 ORTHANC_FORCE_INLINE
250 static void CopyPixel(uint16_t& target, const void* source)
251 {
252 // TODO - check alignement?
253 target = le16toh(*reinterpret_cast<const uint16_t*>(source));
254 }
255
256 ORTHANC_FORCE_INLINE
257 static void CopyPixel(int16_t& target, const void* source)
258 {
259 // byte swapping is the same for unsigned and signed integers
260 // (the sign bit is always stored with the MSByte)
261 uint16_t* targetUp = reinterpret_cast<uint16_t*>(&target);
262 CopyPixel(*targetUp, source);
263 }
264
265 template <typename T>
266 void OrthancMultiframeVolumeLoader::CopyPixelData(const std::string& pixelData)
267 {
268 OrthancStone::ImageBuffer3D& target = volume_->GetPixelData();
269
270 const unsigned int bpp = target.GetBytesPerPixel();
271 const unsigned int width = target.GetWidth();
272 const unsigned int height = target.GetHeight();
273 const unsigned int depth = target.GetDepth();
274
275 if (pixelData.size() != bpp * width * height * depth)
276 {
277 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
278 "The pixel data has not the proper size");
279 }
280
281 if (pixelData.empty())
282 {
283 return;
284 }
285
286 const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData.c_str());
287
288 for (unsigned int z = 0; z < depth; z++)
289 {
290 OrthancStone::ImageBuffer3D::SliceWriter writer(target, OrthancStone::VolumeProjection_Axial, z);
291
292 assert (writer.GetAccessor().GetWidth() == width &&
293 writer.GetAccessor().GetHeight() == height);
294
295 for (unsigned int y = 0; y < height; y++)
296 {
297 assert(sizeof(T) == Orthanc::GetBytesPerPixel(target.GetFormat()));
298
299 T* target = reinterpret_cast<T*>(writer.GetAccessor().GetRow(y));
300
301 for (unsigned int x = 0; x < width; x++)
302 {
303 CopyPixel(*target, source);
304 target ++;
305 source += bpp;
306 }
307 }
308 }
309 }
310
311 void OrthancMultiframeVolumeLoader::SetUncompressedPixelData(const std::string& pixelData)
312 {
313 switch (volume_->GetPixelData().GetFormat())
314 {
315 case Orthanc::PixelFormat_Grayscale32:
316 CopyPixelData<uint32_t>(pixelData);
317 break;
318 case Orthanc::PixelFormat_Grayscale16:
319 CopyPixelData<uint16_t>(pixelData);
320 break;
321 case Orthanc::PixelFormat_SignedGrayscale16:
322 CopyPixelData<int16_t>(pixelData);
323 break;
324 default:
325 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
326 }
327
328 volume_->IncrementRevision();
329
330 pixelDataLoaded_ = true;
331 BroadcastMessage(OrthancStone::DicomVolumeImage::ContentUpdatedMessage(*volume_));
332 }
333
334 bool OrthancMultiframeVolumeLoader::HasGeometry() const
335 {
336 return volume_->HasGeometry();
337 }
338
339 const OrthancStone::VolumeImageGeometry& OrthancMultiframeVolumeLoader::GetImageGeometry() const
340 {
341 return volume_->GetGeometry();
342 }
343
344 OrthancMultiframeVolumeLoader::OrthancMultiframeVolumeLoader(boost::shared_ptr<OrthancStone::DicomVolumeImage> volume,
345 OrthancStone::IOracle& oracle,
346 OrthancStone::IObservable& oracleObservable) :
347 LoaderStateMachine(oracle, oracleObservable),
348 volume_(volume),
349 pixelDataLoaded_(false)
350 {
351 if (volume.get() == NULL)
352 {
353 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
354 }
355 }
356
357 OrthancMultiframeVolumeLoader::~OrthancMultiframeVolumeLoader()
358 {
359 LOG(TRACE) << "OrthancMultiframeVolumeLoader::~OrthancMultiframeVolumeLoader()";
360 }
361
362 void OrthancMultiframeVolumeLoader::LoadInstance(const std::string& instanceId)
363 {
364 Start();
365
366 instanceId_ = instanceId;
367
368 {
369 std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
370 command->SetHttpHeader("Accept-Encoding", "gzip");
371 command->SetUri("/instances/" + instanceId + "/tags");
372 command->AcquirePayload(new LoadGeometry(*this));
373 Schedule(command.release());
374 }
375
376 {
377 std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
378 command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax");
379 command->AcquirePayload(new LoadTransferSyntax(*this));
380 Schedule(command.release());
381 }
382 }
383 }