comparison Framework/Loaders/OrthancMultiframeVolumeLoader.cpp @ 815:df442f1ba0c6

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 28 May 2019 21:59:20 +0200
parents
children 1f85e9c7d020
comparison
equal deleted inserted replaced
814:aead999345e0 815:df442f1ba0c6
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/Toolbox.h>
25
26 namespace OrthancStone
27 {
28 class OrthancMultiframeVolumeLoader::LoadRTDoseGeometry : public LoaderStateMachine::State
29 {
30 private:
31 std::auto_ptr<Orthanc::DicomMap> dicom_;
32
33 public:
34 LoadRTDoseGeometry(OrthancMultiframeVolumeLoader& that,
35 Orthanc::DicomMap* dicom) :
36 State(that),
37 dicom_(dicom)
38 {
39 if (dicom == NULL)
40 {
41 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
42 }
43
44 }
45
46 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
47 {
48 // Complete the DICOM tags with just-received "Grid Frame Offset Vector"
49 std::string s = Orthanc::Toolbox::StripSpaces(message.GetAnswer());
50 dicom_->SetValue(Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, s, false);
51
52 GetLoader<OrthancMultiframeVolumeLoader>().SetGeometry(*dicom_);
53 }
54 };
55
56
57 static std::string GetSopClassUid(const Orthanc::DicomMap& dicom)
58 {
59 std::string s;
60 if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
61 {
62 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
63 "DICOM file without SOP class UID");
64 }
65 else
66 {
67 return s;
68 }
69 }
70
71
72 class OrthancMultiframeVolumeLoader::LoadGeometry : public State
73 {
74 public:
75 LoadGeometry(OrthancMultiframeVolumeLoader& that) :
76 State(that)
77 {
78 }
79
80 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
81 {
82 OrthancMultiframeVolumeLoader& loader = GetLoader<OrthancMultiframeVolumeLoader>();
83
84 Json::Value body;
85 message.ParseJsonBody(body);
86
87 if (body.type() != Json::objectValue)
88 {
89 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
90 }
91
92 std::auto_ptr<Orthanc::DicomMap> dicom(new Orthanc::DicomMap);
93 dicom->FromDicomAsJson(body);
94
95 if (StringToSopClassUid(GetSopClassUid(*dicom)) == SopClassUid_RTDose)
96 {
97 // Download the "Grid Frame Offset Vector" DICOM tag, that is
98 // mandatory for RT-DOSE, but is too long to be returned by default
99
100 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
101 command->SetUri("/instances/" + loader.GetInstanceId() + "/content/" +
102 Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format());
103 command->SetPayload(new LoadRTDoseGeometry(loader, dicom.release()));
104
105 Schedule(command.release());
106 }
107 else
108 {
109 loader.SetGeometry(*dicom);
110 }
111 }
112 };
113
114
115
116 class OrthancMultiframeVolumeLoader::LoadTransferSyntax : public State
117 {
118 public:
119 LoadTransferSyntax(OrthancMultiframeVolumeLoader& that) :
120 State(that)
121 {
122 }
123
124 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
125 {
126 GetLoader<OrthancMultiframeVolumeLoader>().SetTransferSyntax(message.GetAnswer());
127 }
128 };
129
130
131 class OrthancMultiframeVolumeLoader::LoadUncompressedPixelData : public State
132 {
133 public:
134 LoadUncompressedPixelData(OrthancMultiframeVolumeLoader& that) :
135 State(that)
136 {
137 }
138
139 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
140 {
141 GetLoader<OrthancMultiframeVolumeLoader>().SetUncompressedPixelData(message.GetAnswer());
142 }
143 };
144
145
146 const std::string& OrthancMultiframeVolumeLoader::GetInstanceId() const
147 {
148 if (IsActive())
149 {
150 return instanceId_;
151 }
152 else
153 {
154 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
155 }
156 }
157
158
159 void OrthancMultiframeVolumeLoader::ScheduleFrameDownloads()
160 {
161 if (transferSyntaxUid_.empty() ||
162 !volume_->HasGeometry())
163 {
164 return;
165 }
166 /*
167 1.2.840.10008.1.2 Implicit VR Endian: Default Transfer Syntax for DICOM
168 1.2.840.10008.1.2.1 Explicit VR Little Endian
169 1.2.840.10008.1.2.2 Explicit VR Big Endian
170
171 See https://www.dicomlibrary.com/dicom/transfer-syntax/
172 */
173 if (transferSyntaxUid_ == "1.2.840.10008.1.2" ||
174 transferSyntaxUid_ == "1.2.840.10008.1.2.1" ||
175 transferSyntaxUid_ == "1.2.840.10008.1.2.2")
176 {
177 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
178 command->SetHttpHeader("Accept-Encoding", "gzip");
179 command->SetUri("/instances/" + instanceId_ + "/content/" +
180 Orthanc::DICOM_TAG_PIXEL_DATA.Format() + "/0");
181 command->SetPayload(new LoadUncompressedPixelData(*this));
182 Schedule(command.release());
183 }
184 else
185 {
186 throw Orthanc::OrthancException(
187 Orthanc::ErrorCode_NotImplemented,
188 "No support for multiframe instances with transfer syntax: " + transferSyntaxUid_);
189 }
190 }
191
192
193 void OrthancMultiframeVolumeLoader::SetTransferSyntax(const std::string& transferSyntax)
194 {
195 transferSyntaxUid_ = Orthanc::Toolbox::StripSpaces(transferSyntax);
196 ScheduleFrameDownloads();
197 }
198
199
200 void OrthancMultiframeVolumeLoader::SetGeometry(const Orthanc::DicomMap& dicom)
201 {
202 DicomInstanceParameters parameters(dicom);
203 volume_->SetDicomParameters(parameters);
204
205 Orthanc::PixelFormat format;
206 if (!parameters.GetImageInformation().ExtractPixelFormat(format, true))
207 {
208 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
209 }
210
211 double spacingZ;
212 switch (parameters.GetSopClassUid())
213 {
214 case SopClassUid_RTDose:
215 spacingZ = parameters.GetThickness();
216 break;
217
218 default:
219 throw Orthanc::OrthancException(
220 Orthanc::ErrorCode_NotImplemented,
221 "No support for multiframe instances with SOP class UID: " + GetSopClassUid(dicom));
222 }
223
224 const unsigned int width = parameters.GetImageInformation().GetWidth();
225 const unsigned int height = parameters.GetImageInformation().GetHeight();
226 const unsigned int depth = parameters.GetImageInformation().GetNumberOfFrames();
227
228 {
229 VolumeImageGeometry geometry;
230 geometry.SetSize(width, height, depth);
231 geometry.SetAxialGeometry(parameters.GetGeometry());
232 geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(),
233 parameters.GetPixelSpacingY(), spacingZ);
234 volume_->Initialize(geometry, format);
235 }
236
237 volume_->GetPixelData().Clear();
238
239 ScheduleFrameDownloads();
240
241 BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_));
242 }
243
244
245 ORTHANC_FORCE_INLINE
246 static void CopyPixel(uint32_t& target,
247 const void* source)
248 {
249 // TODO - check alignement?
250 target = le32toh(*reinterpret_cast<const uint32_t*>(source));
251 }
252
253
254 template <typename T>
255 void OrthancMultiframeVolumeLoader::CopyPixelData(const std::string& pixelData)
256 {
257 ImageBuffer3D& target = volume_->GetPixelData();
258
259 const unsigned int bpp = target.GetBytesPerPixel();
260 const unsigned int width = target.GetWidth();
261 const unsigned int height = target.GetHeight();
262 const unsigned int depth = target.GetDepth();
263
264 if (pixelData.size() != bpp * width * height * depth)
265 {
266 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
267 "The pixel data has not the proper size");
268 }
269
270 if (pixelData.empty())
271 {
272 return;
273 }
274
275 const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData.c_str());
276
277 for (unsigned int z = 0; z < depth; z++)
278 {
279 ImageBuffer3D::SliceWriter writer(target, VolumeProjection_Axial, z);
280
281 assert (writer.GetAccessor().GetWidth() == width &&
282 writer.GetAccessor().GetHeight() == height);
283
284 for (unsigned int y = 0; y < height; y++)
285 {
286 assert(sizeof(T) == Orthanc::GetBytesPerPixel(target.GetFormat()));
287
288 T* target = reinterpret_cast<T*>(writer.GetAccessor().GetRow(y));
289
290 for (unsigned int x = 0; x < width; x++)
291 {
292 CopyPixel(*target, source);
293
294 target ++;
295 source += bpp;
296 }
297 }
298 }
299 }
300
301
302 void OrthancMultiframeVolumeLoader::SetUncompressedPixelData(const std::string& pixelData)
303 {
304 switch (volume_->GetPixelData().GetFormat())
305 {
306 case Orthanc::PixelFormat_Grayscale32:
307 CopyPixelData<uint32_t>(pixelData);
308 break;
309
310 default:
311 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
312 }
313
314 volume_->IncrementRevision();
315
316 BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_));
317 }
318
319
320 OrthancMultiframeVolumeLoader::OrthancMultiframeVolumeLoader(const boost::shared_ptr<DicomVolumeImage>& volume,
321 IOracle& oracle,
322 IObservable& oracleObservable) :
323 LoaderStateMachine(oracle, oracleObservable),
324 IObservable(oracleObservable.GetBroker()),
325 volume_(volume)
326 {
327 if (volume.get() == NULL)
328 {
329 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
330 }
331 }
332
333
334 void OrthancMultiframeVolumeLoader::LoadInstance(const std::string& instanceId)
335 {
336 Start();
337
338 instanceId_ = instanceId;
339
340 {
341 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
342 command->SetHttpHeader("Accept-Encoding", "gzip");
343 command->SetUri("/instances/" + instanceId + "/tags");
344 command->SetPayload(new LoadGeometry(*this));
345 Schedule(command.release());
346 }
347
348 {
349 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
350 command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax");
351 command->SetPayload(new LoadTransferSyntax(*this));
352 Schedule(command.release());
353 }
354 }
355 }