comparison OrthancStone/Sources/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp @ 1512:244ad1e4e76a

reorganization of folders
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 07 Jul 2020 16:21:02 +0200
parents Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp@28c64c246312
children 4fb8fdf03314
comparison
equal deleted inserted replaced
1511:9dfeee74c1e6 1512:244ad1e4e76a
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-2020 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 "../StoneException.h"
25 #include "../Loaders/ILoadersContext.h"
26 #include "../Loaders/BasicFetchingItemsSorter.h"
27 #include "../Loaders/BasicFetchingStrategy.h"
28 #include "../Toolbox/GeometryToolbox.h"
29 #include "../Volumes/DicomVolumeImageMPRSlicer.h"
30
31 #include <Compatibility.h>
32 #include <Images/ImageProcessing.h>
33 #include <OrthancException.h>
34
35
36 namespace OrthancStone
37 {
38 using OrthancStone::ILoadersContext;
39
40 class OrthancSeriesVolumeProgressiveLoader::ExtractedSlice : public OrthancStone::DicomVolumeImageMPRSlicer::Slice
41 {
42 private:
43 const OrthancSeriesVolumeProgressiveLoader& that_;
44
45 public:
46 ExtractedSlice(const OrthancSeriesVolumeProgressiveLoader& that,
47 const OrthancStone::CoordinateSystem3D& plane) :
48 OrthancStone::DicomVolumeImageMPRSlicer::Slice(*that.volume_, plane),
49 that_(that)
50 {
51 if (IsValid())
52 {
53 if (GetProjection() == OrthancStone::VolumeProjection_Axial)
54 {
55 // For coronal and sagittal projections, we take the global
56 // revision of the volume because even if a single slice changes,
57 // this means the projection will yield a different result -->
58 // we must increase the revision as soon as any slice changes
59 SetRevision(that_.seriesGeometry_.GetSliceRevision(GetSliceIndex()));
60 }
61
62 if (that_.strategy_.get() != NULL &&
63 GetProjection() == OrthancStone::VolumeProjection_Axial)
64 {
65 that_.strategy_->SetCurrent(GetSliceIndex());
66 }
67 }
68 }
69 };
70
71 void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSlice(
72 size_t index, const OrthancStone::DicomInstanceParameters& reference) const
73 {
74 const OrthancStone::DicomInstanceParameters& slice = *slices_[index];
75
76 if (!OrthancStone::GeometryToolbox::IsParallel(
77 reference.GetGeometry().GetNormal(),
78 slice.GetGeometry().GetNormal()))
79 {
80 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
81 "A slice in the volume image is not parallel to the others");
82 }
83
84 if (reference.GetExpectedPixelFormat() != slice.GetExpectedPixelFormat())
85 {
86 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat,
87 "The pixel format changes across the slices of the volume image");
88 }
89
90 if (reference.GetImageInformation().GetWidth() != slice.GetImageInformation().GetWidth() ||
91 reference.GetImageInformation().GetHeight() != slice.GetImageInformation().GetHeight())
92 {
93 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize,
94 "The width/height of slices are not constant in the volume image");
95 }
96
97 if (!OrthancStone::LinearAlgebra::IsNear(reference.GetPixelSpacingX(), slice.GetPixelSpacingX()) ||
98 !OrthancStone::LinearAlgebra::IsNear(reference.GetPixelSpacingY(), slice.GetPixelSpacingY()))
99 {
100 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
101 "The pixel spacing of the slices change across the volume image");
102 }
103 }
104
105
106 void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckVolume() const
107 {
108 for (size_t i = 0; i < slices_.size(); i++)
109 {
110 assert(slices_[i] != NULL);
111 if (slices_[i]->GetImageInformation().GetNumberOfFrames() != 1)
112 {
113 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
114 "This class does not support multi-frame images");
115 }
116 }
117
118 if (slices_.size() != 0)
119 {
120 const OrthancStone::DicomInstanceParameters& reference = *slices_[0];
121
122 for (size_t i = 1; i < slices_.size(); i++)
123 {
124 CheckSlice(i, reference);
125 }
126 }
127 }
128
129
130 void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::Clear()
131 {
132 for (size_t i = 0; i < slices_.size(); i++)
133 {
134 assert(slices_[i] != NULL);
135 delete slices_[i];
136 }
137
138 slices_.clear();
139 slicesRevision_.clear();
140 }
141
142
143 void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSliceIndex(size_t index) const
144 {
145 if (!HasGeometry())
146 {
147 LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSliceIndex(size_t index): (!HasGeometry())";
148 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
149 }
150 else if (index >= slices_.size())
151 {
152 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
153 }
154 else
155 {
156 assert(slices_.size() == GetImageGeometry().GetDepth() &&
157 slices_.size() == slicesRevision_.size());
158 }
159 }
160
161
162 // WARNING: The payload of "slices" must be of class "DicomInstanceParameters"
163 // (called with the slices created in LoadGeometry)
164 void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::ComputeGeometry(OrthancStone::SlicesSorter& slices)
165 {
166 Clear();
167
168 if (!slices.Sort())
169 {
170 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
171 "Cannot sort the 3D slices of a DICOM series");
172 }
173
174 if (slices.GetSlicesCount() == 0)
175 {
176 geometry_.reset(new OrthancStone::VolumeImageGeometry);
177 }
178 else
179 {
180 slices_.reserve(slices.GetSlicesCount());
181 slicesRevision_.resize(slices.GetSlicesCount(), 0);
182
183 for (size_t i = 0; i < slices.GetSlicesCount(); i++)
184 {
185 const OrthancStone::DicomInstanceParameters& slice =
186 dynamic_cast<const OrthancStone::DicomInstanceParameters&>(slices.GetSlicePayload(i));
187 slices_.push_back(new OrthancStone::DicomInstanceParameters(slice));
188 }
189
190 CheckVolume();
191
192 double spacingZ;
193
194 if (slices.ComputeSpacingBetweenSlices(spacingZ))
195 {
196 LOG(TRACE) << "Computed spacing between slices: " << spacingZ << "mm";
197
198 const OrthancStone::DicomInstanceParameters& parameters = *slices_[0];
199
200 geometry_.reset(new OrthancStone::VolumeImageGeometry);
201 geometry_->SetSizeInVoxels(parameters.GetImageInformation().GetWidth(),
202 parameters.GetImageInformation().GetHeight(),
203 static_cast<unsigned int>(slices.GetSlicesCount()));
204 geometry_->SetAxialGeometry(slices.GetSliceGeometry(0));
205 geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(),
206 parameters.GetPixelSpacingY(), spacingZ);
207 }
208 else
209 {
210 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
211 "The origins of the slices of a volume image are not regularly spaced");
212 }
213 }
214 }
215
216
217 const OrthancStone::VolumeImageGeometry& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetImageGeometry() const
218 {
219 if (!HasGeometry())
220 {
221 LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetImageGeometry(): (!HasGeometry())";
222 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
223 }
224 else
225 {
226 assert(slices_.size() == geometry_->GetDepth());
227 return *geometry_;
228 }
229 }
230
231
232 const OrthancStone::DicomInstanceParameters& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceParameters(size_t index) const
233 {
234 CheckSliceIndex(index);
235 return *slices_[index];
236 }
237
238
239 uint64_t OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceRevision(size_t index) const
240 {
241 CheckSliceIndex(index);
242 return slicesRevision_[index];
243 }
244
245
246 void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::IncrementSliceRevision(size_t index)
247 {
248 CheckSliceIndex(index);
249 slicesRevision_[index] ++;
250 }
251
252
253 static unsigned int GetSliceIndexPayload(const OrthancStone::OracleCommandBase& command)
254 {
255 assert(command.HasPayload());
256 return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue();
257 }
258
259
260 void OrthancSeriesVolumeProgressiveLoader::ScheduleNextSliceDownload()
261 {
262 assert(strategy_.get() != NULL);
263
264 unsigned int sliceIndex = 0, quality = 0;
265
266 if (strategy_->GetNext(sliceIndex, quality))
267 {
268 if (!progressiveQuality_)
269 {
270 ORTHANC_ASSERT(quality == QUALITY_00, "INTERNAL ERROR. quality != QUALITY_00 in "
271 << "OrthancSeriesVolumeProgressiveLoader::ScheduleNextSliceDownload");
272 }
273
274 const OrthancStone::DicomInstanceParameters& slice = seriesGeometry_.GetSliceParameters(sliceIndex);
275
276 const std::string& instance = slice.GetOrthancInstanceIdentifier();
277 if (instance.empty())
278 {
279 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
280 }
281
282 std::unique_ptr<OrthancStone::OracleCommandBase> command;
283
284 if (!progressiveQuality_ || quality == QUALITY_02)
285 {
286 std::unique_ptr<OrthancStone::GetOrthancImageCommand> tmp(new OrthancStone::GetOrthancImageCommand);
287 // TODO: review the following comment.
288 // - Commented out by bgo on 2019-07-19 | reason: Alain has seen cases
289 // where gzipping the uint16 image took 11 sec to produce 5mb.
290 // The unzipped request was much much faster.
291 // - Re-enabled on 2019-07-30. Reason: in Web Assembly, the browser
292 // does not use the Accept-Encoding header and always requests
293 // compression. Furthermore, NOT
294 tmp->SetHttpHeader("Accept-Encoding", "gzip");
295 tmp->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
296 tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat());
297 tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
298 //LOG(INFO)
299 // << "OrthancSeriesVolumeProgressiveLoader.ScheduleNextSliceDownload()"
300 // << " sliceIndex = " << sliceIndex << " slice quality = " << quality
301 // << " URI = " << tmp->GetUri();
302 command.reset(tmp.release());
303 }
304 else // progressive mode is true AND quality is not final (different from QUALITY_02
305 {
306 std::unique_ptr<OrthancStone::GetOrthancWebViewerJpegCommand> tmp(
307 new OrthancStone::GetOrthancWebViewerJpegCommand);
308
309 // TODO: review the following comment. Commented out by bgo on 2019-07-19
310 // (gzip for jpeg seems overkill)
311 //tmp->SetHttpHeader("Accept-Encoding", "gzip");
312 tmp->SetInstance(instance);
313 tmp->SetQuality((quality == 0 ? 50 : 90)); // QUALITY_00 is Jpeg50 while QUALITY_01 is Jpeg90
314 tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
315 LOG(TRACE)
316 << "OrthancSeriesVolumeProgressiveLoader.ScheduleNextSliceDownload()"
317 << " sliceIndex = " << sliceIndex << " slice quality = " << quality;
318 command.reset(tmp.release());
319 }
320
321 command->AcquirePayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex));
322
323 {
324 std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock());
325 boost::shared_ptr<IObserver> observer(GetSharedObserver());
326 lock->Schedule(observer, sliceSchedulingPriority_, command.release());
327 }
328 }
329 else
330 {
331 // loading is finished!
332 volumeImageReadyInHighQuality_ = true;
333 BroadcastMessage(OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality(*this));
334 }
335 }
336
337 /**
338 This is called in response to GET "/series/XXXXXXXXXXXXX/instances-tags"
339 */
340 void OrthancSeriesVolumeProgressiveLoader::LoadGeometry(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
341 {
342 Json::Value body;
343 message.ParseJsonBody(body);
344
345 if (body.type() != Json::objectValue)
346 {
347 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
348 }
349
350 {
351 Json::Value::Members instances = body.getMemberNames();
352
353 OrthancStone::SlicesSorter slices;
354
355 for (size_t i = 0; i < instances.size(); i++)
356 {
357 Orthanc::DicomMap dicom;
358 dicom.FromDicomAsJson(body[instances[i]]);
359
360 std::unique_ptr<OrthancStone::DicomInstanceParameters> instance(new OrthancStone::DicomInstanceParameters(dicom));
361 instance->SetOrthancInstanceIdentifier(instances[i]);
362
363 // the 3D plane corresponding to the slice
364 OrthancStone::CoordinateSystem3D geometry = instance->GetGeometry();
365 slices.AddSlice(geometry, instance.release());
366
367 if (slicePostProcessor_)
368 slicePostProcessor_->ProcessCTDicomSlice(dicom);
369 }
370
371 seriesGeometry_.ComputeGeometry(slices);
372 }
373
374 size_t slicesCount = seriesGeometry_.GetImageGeometry().GetDepth();
375
376 if (slicesCount == 0)
377 {
378 volume_->Initialize(seriesGeometry_.GetImageGeometry(), Orthanc::PixelFormat_Grayscale8);
379 }
380 else
381 {
382 const OrthancStone::DicomInstanceParameters& parameters = seriesGeometry_.GetSliceParameters(0);
383
384 volume_->Initialize(seriesGeometry_.GetImageGeometry(), parameters.GetExpectedPixelFormat());
385 volume_->SetDicomParameters(parameters);
386 volume_->GetPixelData().Clear();
387
388 // If we are in progressive mode, the Fetching strategy will first request QUALITY_00, then QUALITY_01, then
389 // QUALITY_02... Otherwise, it's only QUALITY_00
390 unsigned int maxQuality = QUALITY_00;
391 if (progressiveQuality_)
392 maxQuality = QUALITY_02;
393
394 strategy_.reset(new OrthancStone::BasicFetchingStrategy(
395 sorter_->CreateSorter(static_cast<unsigned int>(slicesCount)),
396 maxQuality));
397
398 assert(simultaneousDownloads_ != 0);
399 for (unsigned int i = 0; i < simultaneousDownloads_; i++)
400 {
401 ScheduleNextSliceDownload();
402 }
403 }
404
405 slicesQuality_.resize(slicesCount, 0);
406
407 BroadcastMessage(OrthancStone::DicomVolumeImage::GeometryReadyMessage(*volume_));
408 }
409
410
411 void OrthancSeriesVolumeProgressiveLoader::SetSliceContent(unsigned int sliceIndex,
412 const Orthanc::ImageAccessor& image,
413 unsigned int quality)
414 {
415 ORTHANC_ASSERT(sliceIndex < slicesQuality_.size() &&
416 slicesQuality_.size() == volume_->GetPixelData().GetDepth());
417
418 if (!progressiveQuality_)
419 {
420 ORTHANC_ASSERT(quality == QUALITY_00);
421 ORTHANC_ASSERT(slicesQuality_[sliceIndex] == QUALITY_00);
422 }
423
424 if (quality >= slicesQuality_[sliceIndex])
425 {
426 {
427 OrthancStone::ImageBuffer3D::SliceWriter writer(volume_->GetPixelData(),
428 OrthancStone::VolumeProjection_Axial,
429 sliceIndex);
430
431 Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image);
432 }
433
434 volume_->IncrementRevision();
435 seriesGeometry_.IncrementSliceRevision(sliceIndex);
436 slicesQuality_[sliceIndex] = quality;
437
438 BroadcastMessage(OrthancStone::DicomVolumeImage::ContentUpdatedMessage(*volume_));
439 }
440 LOG(TRACE) << "SetSliceContent sliceIndex = " << sliceIndex << " -- will "
441 << " now call ScheduleNextSliceDownload()";
442 ScheduleNextSliceDownload();
443 }
444
445 void OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent(
446 const OrthancStone::GetOrthancImageCommand::SuccessMessage& message)
447 {
448 unsigned int quality = QUALITY_00;
449 if (progressiveQuality_)
450 quality = QUALITY_02;
451
452 SetSliceContent(GetSliceIndexPayload(message.GetOrigin()),
453 message.GetImage(),
454 quality);
455 }
456
457 void OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent(
458 const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message)
459 {
460 ORTHANC_ASSERT(progressiveQuality_, "INTERNAL ERROR: OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent"
461 << " called while progressiveQuality_ is false!");
462
463 LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent";
464 unsigned int quality;
465
466 switch (dynamic_cast<const OrthancStone::GetOrthancWebViewerJpegCommand&>(message.GetOrigin()).GetQuality())
467 {
468 case 50:
469 quality = QUALITY_00;
470 break;
471
472 case 90:
473 quality = QUALITY_01;
474 break;
475
476 default:
477 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
478 }
479
480 SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality);
481 }
482
483
484 void OrthancSeriesVolumeProgressiveLoader::SetMetadataSchedulingPriority(int p)
485 {
486 medadataSchedulingPriority_ = p;
487 }
488
489 int OrthancSeriesVolumeProgressiveLoader::GetMetadataSchedulingPriority() const
490 {
491 return medadataSchedulingPriority_;
492 }
493
494 void OrthancSeriesVolumeProgressiveLoader::SetSliceSchedulingPriority(int p)
495 {
496 sliceSchedulingPriority_ = p;
497 }
498
499 int OrthancSeriesVolumeProgressiveLoader::GetSliceSchedulingPriority() const
500 {
501 return sliceSchedulingPriority_;
502 }
503
504 void OrthancSeriesVolumeProgressiveLoader::SetSchedulingPriority(int p)
505 {
506 medadataSchedulingPriority_ = p;
507 sliceSchedulingPriority_ = p;
508 }
509
510 OrthancSeriesVolumeProgressiveLoader::OrthancSeriesVolumeProgressiveLoader(
511 OrthancStone::ILoadersContext& loadersContext,
512 boost::shared_ptr<OrthancStone::DicomVolumeImage> volume,
513 bool progressiveQuality)
514 : loadersContext_(loadersContext)
515 , active_(false)
516 , progressiveQuality_(progressiveQuality)
517 , simultaneousDownloads_(4)
518 , volume_(volume)
519 , sorter_(new OrthancStone::BasicFetchingItemsSorter::Factory)
520 , volumeImageReadyInHighQuality_(false)
521 , medadataSchedulingPriority_(0)
522 , sliceSchedulingPriority_(0)
523 {
524 }
525
526 boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader>
527 OrthancSeriesVolumeProgressiveLoader::Create(
528 OrthancStone::ILoadersContext& loadersContext,
529 boost::shared_ptr<OrthancStone::DicomVolumeImage> volume,
530 bool progressiveQuality)
531 {
532 std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext.Lock());
533
534 boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> obj(
535 new OrthancSeriesVolumeProgressiveLoader(
536 loadersContext, volume, progressiveQuality));
537
538 obj->Register<OrthancStone::OrthancRestApiCommand::SuccessMessage>(
539 lock->GetOracleObservable(),
540 &OrthancSeriesVolumeProgressiveLoader::LoadGeometry);
541
542 obj->Register<OrthancStone::GetOrthancImageCommand::SuccessMessage>(
543 lock->GetOracleObservable(),
544 &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent);
545
546 obj->Register<OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage>(
547 lock->GetOracleObservable(),
548 &OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent);
549
550 return obj;
551 }
552
553
554 OrthancSeriesVolumeProgressiveLoader::~OrthancSeriesVolumeProgressiveLoader()
555 {
556 LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::~OrthancSeriesVolumeProgressiveLoader()";
557 }
558
559 void OrthancSeriesVolumeProgressiveLoader::SetSimultaneousDownloads(unsigned int count)
560 {
561 if (active_)
562 {
563 LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::SetSimultaneousDownloads(): (active_)";
564 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
565 }
566 else if (count == 0)
567 {
568 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
569 }
570 else
571 {
572 simultaneousDownloads_ = count;
573 }
574 }
575
576
577 void OrthancSeriesVolumeProgressiveLoader::LoadSeries(const std::string& seriesId)
578 {
579 if (active_)
580 {
581 LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries(const std::string& seriesId): (active_)";
582 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
583 }
584 else
585 {
586 active_ = true;
587
588 std::unique_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
589 command->SetUri("/series/" + seriesId + "/instances-tags");
590 {
591 std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock());
592 boost::shared_ptr<IObserver> observer(GetSharedObserver());
593 lock->Schedule(observer, medadataSchedulingPriority_, command.release());
594 }
595 }
596 }
597
598
599 OrthancStone::IVolumeSlicer::IExtractedSlice*
600 OrthancSeriesVolumeProgressiveLoader::ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane)
601 {
602 if (volume_->HasGeometry())
603 {
604 return new ExtractedSlice(*this, cuttingPlane);
605 }
606 else
607 {
608 return new IVolumeSlicer::InvalidSlice;
609 }
610 }
611 }