815
|
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 }
|