comparison Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp @ 860:238693c3bc51 am-dev

merge default -> am-dev
author Alain Mazy <alain@mazy.be>
date Mon, 24 Jun 2019 14:35:00 +0200
parents a68cd7ae8838
children 2b4b6b86520a
comparison
equal deleted inserted replaced
856:a6e17a5a39e7 860:238693c3bc51
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 "OrthancSeriesVolumeProgressiveLoader.h"
23
24 #include "../Toolbox/GeometryToolbox.h"
25 #include "../Volumes/DicomVolumeImageMPRSlicer.h"
26 #include "BasicFetchingItemsSorter.h"
27 #include "BasicFetchingStrategy.h"
28
29 #include <Core/Images/ImageProcessing.h>
30 #include <Core/OrthancException.h>
31
32 namespace OrthancStone
33 {
34 class OrthancSeriesVolumeProgressiveLoader::ExtractedSlice : public DicomVolumeImageMPRSlicer::Slice
35 {
36 private:
37 const OrthancSeriesVolumeProgressiveLoader& that_;
38
39 public:
40 ExtractedSlice(const OrthancSeriesVolumeProgressiveLoader& that,
41 const CoordinateSystem3D& plane) :
42 DicomVolumeImageMPRSlicer::Slice(*that.volume_, plane),
43 that_(that)
44 {
45 if (IsValid())
46 {
47 if (GetProjection() == VolumeProjection_Axial)
48 {
49 // For coronal and sagittal projections, we take the global
50 // revision of the volume because even if a single slice changes,
51 // this means the projection will yield a different result -->
52 // we must increase the revision as soon as any slice changes
53 SetRevision(that_.seriesGeometry_.GetSliceRevision(GetSliceIndex()));
54 }
55
56 if (that_.strategy_.get() != NULL &&
57 GetProjection() == VolumeProjection_Axial)
58 {
59 that_.strategy_->SetCurrent(GetSliceIndex());
60 }
61 }
62 }
63 };
64
65
66
67 void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSlice(size_t index,
68 const DicomInstanceParameters& reference) const
69 {
70 const DicomInstanceParameters& slice = *slices_[index];
71
72 if (!GeometryToolbox::IsParallel(
73 reference.GetGeometry().GetNormal(),
74 slice.GetGeometry().GetNormal()))
75 {
76 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
77 "A slice in the volume image is not parallel to the others");
78 }
79
80 if (reference.GetExpectedPixelFormat() != slice.GetExpectedPixelFormat())
81 {
82 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat,
83 "The pixel format changes across the slices of the volume image");
84 }
85
86 if (reference.GetImageInformation().GetWidth() != slice.GetImageInformation().GetWidth() ||
87 reference.GetImageInformation().GetHeight() != slice.GetImageInformation().GetHeight())
88 {
89 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize,
90 "The width/height of slices are not constant in the volume image");
91 }
92
93 if (!LinearAlgebra::IsNear(reference.GetPixelSpacingX(), slice.GetPixelSpacingX()) ||
94 !LinearAlgebra::IsNear(reference.GetPixelSpacingY(), slice.GetPixelSpacingY()))
95 {
96 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
97 "The pixel spacing of the slices change across the volume image");
98 }
99 }
100
101
102 void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckVolume() const
103 {
104 for (size_t i = 0; i < slices_.size(); i++)
105 {
106 assert(slices_[i] != NULL);
107 if (slices_[i]->GetImageInformation().GetNumberOfFrames() != 1)
108 {
109 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
110 "This class does not support multi-frame images");
111 }
112 }
113
114 if (slices_.size() != 0)
115 {
116 const DicomInstanceParameters& reference = *slices_[0];
117
118 for (size_t i = 1; i < slices_.size(); i++)
119 {
120 CheckSlice(i, reference);
121 }
122 }
123 }
124
125
126 void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::Clear()
127 {
128 for (size_t i = 0; i < slices_.size(); i++)
129 {
130 assert(slices_[i] != NULL);
131 delete slices_[i];
132 }
133
134 slices_.clear();
135 slicesRevision_.clear();
136 }
137
138
139 void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSliceIndex(size_t index) const
140 {
141 if (!HasGeometry())
142 {
143 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
144 }
145 else if (index >= slices_.size())
146 {
147 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
148 }
149 else
150 {
151 assert(slices_.size() == GetImageGeometry().GetDepth() &&
152 slices_.size() == slicesRevision_.size());
153 }
154 }
155
156
157 // WARNING: The payload of "slices" must be of class "DicomInstanceParameters"
158 // (called with the slices created in LoadGeometry)
159 void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::ComputeGeometry(SlicesSorter& slices)
160 {
161 Clear();
162
163 if (!slices.Sort())
164 {
165 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
166 "Cannot sort the 3D slices of a DICOM series");
167 }
168
169 if (slices.GetSlicesCount() == 0)
170 {
171 geometry_.reset(new VolumeImageGeometry);
172 }
173 else
174 {
175 slices_.reserve(slices.GetSlicesCount());
176 slicesRevision_.resize(slices.GetSlicesCount(), 0);
177
178 for (size_t i = 0; i < slices.GetSlicesCount(); i++)
179 {
180 const DicomInstanceParameters& slice =
181 dynamic_cast<const DicomInstanceParameters&>(slices.GetSlicePayload(i));
182 slices_.push_back(new DicomInstanceParameters(slice));
183 }
184
185 CheckVolume();
186
187 const double spacingZ = slices.ComputeSpacingBetweenSlices();
188 LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm";
189
190 const DicomInstanceParameters& parameters = *slices_[0];
191
192 geometry_.reset(new VolumeImageGeometry);
193 geometry_->SetSize(parameters.GetImageInformation().GetWidth(),
194 parameters.GetImageInformation().GetHeight(),
195 static_cast<unsigned int>(slices.GetSlicesCount()));
196 geometry_->SetAxialGeometry(slices.GetSliceGeometry(0));
197 geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(),
198 parameters.GetPixelSpacingY(), spacingZ);
199 }
200 }
201
202
203 const VolumeImageGeometry& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetImageGeometry() const
204 {
205 if (!HasGeometry())
206 {
207 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
208 }
209 else
210 {
211 assert(slices_.size() == geometry_->GetDepth());
212 return *geometry_;
213 }
214 }
215
216
217 const DicomInstanceParameters& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceParameters(size_t index) const
218 {
219 CheckSliceIndex(index);
220 return *slices_[index];
221 }
222
223
224 uint64_t OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceRevision(size_t index) const
225 {
226 CheckSliceIndex(index);
227 return slicesRevision_[index];
228 }
229
230
231 void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::IncrementSliceRevision(size_t index)
232 {
233 CheckSliceIndex(index);
234 slicesRevision_[index] ++;
235 }
236
237
238 static unsigned int GetSliceIndexPayload(const OracleCommandWithPayload& command)
239 {
240 return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue();
241 }
242
243
244 void OrthancSeriesVolumeProgressiveLoader::ScheduleNextSliceDownload()
245 {
246 assert(strategy_.get() != NULL);
247
248 unsigned int sliceIndex, quality;
249
250 if (strategy_->GetNext(sliceIndex, quality))
251 {
252 assert(quality <= BEST_QUALITY);
253
254 const DicomInstanceParameters& slice = seriesGeometry_.GetSliceParameters(sliceIndex);
255
256 const std::string& instance = slice.GetOrthancInstanceIdentifier();
257 if (instance.empty())
258 {
259 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
260 }
261
262 std::auto_ptr<OracleCommandWithPayload> command;
263
264 if (quality == BEST_QUALITY)
265 {
266 std::auto_ptr<GetOrthancImageCommand> tmp(new GetOrthancImageCommand);
267 tmp->SetHttpHeader("Accept-Encoding", "gzip");
268 tmp->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
269 tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat());
270 tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
271 command.reset(tmp.release());
272 }
273 else
274 {
275 std::auto_ptr<GetOrthancWebViewerJpegCommand> tmp(new GetOrthancWebViewerJpegCommand);
276 tmp->SetHttpHeader("Accept-Encoding", "gzip");
277 tmp->SetInstance(instance);
278 tmp->SetQuality((quality == 0 ? 50 : 90));
279 tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
280 command.reset(tmp.release());
281 }
282
283 command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex));
284 oracle_.Schedule(*this, command.release());
285 }
286 }
287
288 /**
289 This is called in response to GET "/series/XXXXXXXXXXXXX/instances-tags"
290 */
291 void OrthancSeriesVolumeProgressiveLoader::LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message)
292 {
293 Json::Value body;
294 message.ParseJsonBody(body);
295
296 if (body.type() != Json::objectValue)
297 {
298 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
299 }
300
301 {
302 Json::Value::Members instances = body.getMemberNames();
303
304 SlicesSorter slices;
305
306 for (size_t i = 0; i < instances.size(); i++)
307 {
308 Orthanc::DicomMap dicom;
309 dicom.FromDicomAsJson(body[instances[i]]);
310
311 std::auto_ptr<DicomInstanceParameters> instance(new DicomInstanceParameters(dicom));
312 instance->SetOrthancInstanceIdentifier(instances[i]);
313
314 // the 3D plane corresponding to the slice
315 CoordinateSystem3D geometry = instance->GetGeometry();
316 slices.AddSlice(geometry, instance.release());
317 }
318
319 seriesGeometry_.ComputeGeometry(slices);
320 }
321
322 size_t slicesCount = seriesGeometry_.GetImageGeometry().GetDepth();
323
324 if (slicesCount == 0)
325 {
326 volume_->Initialize(seriesGeometry_.GetImageGeometry(), Orthanc::PixelFormat_Grayscale8);
327 }
328 else
329 {
330 const DicomInstanceParameters& parameters = seriesGeometry_.GetSliceParameters(0);
331
332 volume_->Initialize(seriesGeometry_.GetImageGeometry(), parameters.GetExpectedPixelFormat());
333 volume_->SetDicomParameters(parameters);
334 volume_->GetPixelData().Clear();
335
336 strategy_.reset(new BasicFetchingStrategy(sorter_->CreateSorter(static_cast<unsigned int>(slicesCount)), BEST_QUALITY));
337
338 assert(simultaneousDownloads_ != 0);
339 for (unsigned int i = 0; i < simultaneousDownloads_; i++)
340 {
341 ScheduleNextSliceDownload();
342 }
343 }
344
345 slicesQuality_.resize(slicesCount, 0);
346
347 BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_));
348 }
349
350
351 void OrthancSeriesVolumeProgressiveLoader::SetSliceContent(unsigned int sliceIndex,
352 const Orthanc::ImageAccessor& image,
353 unsigned int quality)
354 {
355 assert(sliceIndex < slicesQuality_.size() &&
356 slicesQuality_.size() == volume_->GetPixelData().GetDepth());
357
358 if (quality >= slicesQuality_[sliceIndex])
359 {
360 {
361 ImageBuffer3D::SliceWriter writer(volume_->GetPixelData(), VolumeProjection_Axial, sliceIndex);
362 Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image);
363 }
364
365 volume_->IncrementRevision();
366 seriesGeometry_.IncrementSliceRevision(sliceIndex);
367 slicesQuality_[sliceIndex] = quality;
368
369 BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_));
370 }
371
372 ScheduleNextSliceDownload();
373 }
374
375
376 void OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent(const GetOrthancImageCommand::SuccessMessage& message)
377 {
378 SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), BEST_QUALITY);
379 }
380
381
382 void OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message)
383 {
384 unsigned int quality;
385
386 switch (message.GetOrigin().GetQuality())
387 {
388 case 50:
389 quality = LOW_QUALITY;
390 break;
391
392 case 90:
393 quality = MIDDLE_QUALITY;
394 break;
395
396 default:
397 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
398 }
399
400 SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality);
401 }
402
403
404 OrthancSeriesVolumeProgressiveLoader::OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<DicomVolumeImage>& volume,
405 IOracle& oracle,
406 IObservable& oracleObservable) :
407 IObserver(oracleObservable.GetBroker()),
408 IObservable(oracleObservable.GetBroker()),
409 oracle_(oracle),
410 active_(false),
411 simultaneousDownloads_(4),
412 volume_(volume),
413 sorter_(new BasicFetchingItemsSorter::Factory)
414 {
415 oracleObservable.RegisterObserverCallback(
416 new Callable<OrthancSeriesVolumeProgressiveLoader, OrthancRestApiCommand::SuccessMessage>
417 (*this, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry));
418
419 oracleObservable.RegisterObserverCallback(
420 new Callable<OrthancSeriesVolumeProgressiveLoader, GetOrthancImageCommand::SuccessMessage>
421 (*this, &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent));
422
423 oracleObservable.RegisterObserverCallback(
424 new Callable<OrthancSeriesVolumeProgressiveLoader, GetOrthancWebViewerJpegCommand::SuccessMessage>
425 (*this, &OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent));
426 }
427
428
429 void OrthancSeriesVolumeProgressiveLoader::SetSimultaneousDownloads(unsigned int count)
430 {
431 if (active_)
432 {
433 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
434 }
435 else if (count == 0)
436 {
437 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
438 }
439 else
440 {
441 simultaneousDownloads_ = count;
442 }
443 }
444
445
446 void OrthancSeriesVolumeProgressiveLoader::LoadSeries(const std::string& seriesId)
447 {
448 if (active_)
449 {
450 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
451 }
452 else
453 {
454 active_ = true;
455
456 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
457 command->SetUri("/series/" + seriesId + "/instances-tags");
458
459 oracle_.Schedule(*this, command.release());
460 }
461 }
462
463
464 IVolumeSlicer::IExtractedSlice*
465 OrthancSeriesVolumeProgressiveLoader::ExtractSlice(const CoordinateSystem3D& cuttingPlane)
466 {
467 if (volume_->HasGeometry())
468 {
469 return new ExtractedSlice(*this, cuttingPlane);
470 }
471 else
472 {
473 return new IVolumeSlicer::InvalidSlice;
474 }
475 }
476 }