Mercurial > hg > orthanc-stone
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 } |