Mercurial > hg > orthanc-stone
comparison OrthancStone/Sources/Loaders/SeriesFramesLoader.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/SeriesFramesLoader.cpp@121d01aa328e |
children | e731e62692a9 |
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 "SeriesFramesLoader.h" | |
23 | |
24 #include "../Oracle/ParseDicomFromFileCommand.h" | |
25 #include "../Oracle/ParseDicomFromWadoCommand.h" | |
26 | |
27 #if ORTHANC_ENABLE_DCMTK == 1 | |
28 # include <DicomParsing/Internals/DicomImageDecoder.h> | |
29 #endif | |
30 | |
31 #include <DicomFormat/DicomInstanceHasher.h> | |
32 #include <Images/Image.h> | |
33 #include <Images/ImageProcessing.h> | |
34 #include <Images/JpegReader.h> | |
35 | |
36 #include <boost/algorithm/string/predicate.hpp> | |
37 | |
38 namespace OrthancStone | |
39 { | |
40 class SeriesFramesLoader::Payload : public Orthanc::IDynamicObject | |
41 { | |
42 private: | |
43 DicomSource source_; | |
44 size_t seriesIndex_; | |
45 std::string sopInstanceUid_; // Only used for debug purpose | |
46 unsigned int quality_; | |
47 bool hasWindowing_; | |
48 float windowingCenter_; | |
49 float windowingWidth_; | |
50 std::unique_ptr<Orthanc::IDynamicObject> userPayload_; | |
51 | |
52 public: | |
53 Payload(const DicomSource& source, | |
54 size_t seriesIndex, | |
55 const std::string& sopInstanceUid, | |
56 unsigned int quality, | |
57 Orthanc::IDynamicObject* userPayload) : | |
58 source_(source), | |
59 seriesIndex_(seriesIndex), | |
60 sopInstanceUid_(sopInstanceUid), | |
61 quality_(quality), | |
62 hasWindowing_(false), | |
63 userPayload_(userPayload) | |
64 { | |
65 } | |
66 | |
67 size_t GetSeriesIndex() const | |
68 { | |
69 return seriesIndex_; | |
70 } | |
71 | |
72 const std::string& GetSopInstanceUid() const | |
73 { | |
74 return sopInstanceUid_; | |
75 } | |
76 | |
77 unsigned int GetQuality() const | |
78 { | |
79 return quality_; | |
80 } | |
81 | |
82 void SetWindowing(float center, | |
83 float width) | |
84 { | |
85 hasWindowing_ = true; | |
86 windowingCenter_ = center; | |
87 windowingWidth_ = width; | |
88 } | |
89 | |
90 bool HasWindowing() const | |
91 { | |
92 return hasWindowing_; | |
93 } | |
94 | |
95 float GetWindowingCenter() const | |
96 { | |
97 if (hasWindowing_) | |
98 { | |
99 return windowingCenter_; | |
100 } | |
101 else | |
102 { | |
103 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
104 } | |
105 } | |
106 | |
107 float GetWindowingWidth() const | |
108 { | |
109 if (hasWindowing_) | |
110 { | |
111 return windowingWidth_; | |
112 } | |
113 else | |
114 { | |
115 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
116 } | |
117 } | |
118 | |
119 const DicomSource& GetSource() const | |
120 { | |
121 return source_; | |
122 } | |
123 | |
124 Orthanc::IDynamicObject* GetUserPayload() const | |
125 { | |
126 return userPayload_.get(); | |
127 } | |
128 }; | |
129 | |
130 | |
131 SeriesFramesLoader::SeriesFramesLoader(ILoadersContext& context, | |
132 LoadedDicomResources& instances, | |
133 const std::string& dicomDirPath, | |
134 boost::shared_ptr<LoadedDicomResources> dicomDir) : | |
135 context_(context), | |
136 frames_(instances), | |
137 dicomDirPath_(dicomDirPath), | |
138 dicomDir_(dicomDir) | |
139 { | |
140 } | |
141 | |
142 | |
143 void SeriesFramesLoader::EmitMessage(const Payload& payload, | |
144 const Orthanc::ImageAccessor& image) | |
145 { | |
146 const DicomInstanceParameters& parameters = frames_.GetInstanceParameters(payload.GetSeriesIndex()); | |
147 const Orthanc::DicomMap& instance = frames_.GetInstance(payload.GetSeriesIndex()); | |
148 size_t frameIndex = frames_.GetFrameIndex(payload.GetSeriesIndex()); | |
149 | |
150 if (frameIndex >= parameters.GetImageInformation().GetNumberOfFrames() || | |
151 payload.GetSopInstanceUid() != parameters.GetSopInstanceUid()) | |
152 { | |
153 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
154 } | |
155 | |
156 LOG(TRACE) << "Decoded instance " << payload.GetSopInstanceUid() << ", frame " | |
157 << frameIndex << ": " << image.GetWidth() << "x" | |
158 << image.GetHeight() << ", " << Orthanc::EnumerationToString(image.GetFormat()) | |
159 << ", quality " << payload.GetQuality(); | |
160 | |
161 FrameLoadedMessage message(*this, frameIndex, payload.GetQuality(), image, instance, parameters, payload.GetUserPayload()); | |
162 BroadcastMessage(message); | |
163 } | |
164 | |
165 | |
166 #if ORTHANC_ENABLE_DCMTK == 1 | |
167 void SeriesFramesLoader::HandleDicom(const Payload& payload, | |
168 Orthanc::ParsedDicomFile& dicom) | |
169 { | |
170 size_t frameIndex = frames_.GetFrameIndex(payload.GetSeriesIndex()); | |
171 | |
172 std::unique_ptr<Orthanc::ImageAccessor> decoded; | |
173 decoded.reset(Orthanc::DicomImageDecoder::Decode( | |
174 dicom, | |
175 static_cast<unsigned int>(frameIndex))); | |
176 | |
177 if (decoded.get() == NULL) | |
178 { | |
179 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
180 } | |
181 | |
182 EmitMessage(payload, *decoded); | |
183 } | |
184 #endif | |
185 | |
186 | |
187 void SeriesFramesLoader::HandleDicomWebRendered(const Payload& payload, | |
188 const std::string& body, | |
189 const std::map<std::string, std::string>& headers) | |
190 { | |
191 assert(payload.GetSource().IsDicomWeb() && | |
192 payload.HasWindowing()); | |
193 | |
194 bool ok = false; | |
195 for (std::map<std::string, std::string>::const_iterator it = headers.begin(); | |
196 it != headers.end(); ++it) | |
197 { | |
198 if (boost::iequals("content-type", it->first) && | |
199 boost::iequals(Orthanc::MIME_JPEG, it->second)) | |
200 { | |
201 ok = true; | |
202 break; | |
203 } | |
204 } | |
205 | |
206 if (!ok) | |
207 { | |
208 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, | |
209 "The WADO-RS server has not generated a JPEG image on /rendered"); | |
210 } | |
211 | |
212 Orthanc::JpegReader reader; | |
213 reader.ReadFromMemory(body); | |
214 | |
215 switch (reader.GetFormat()) | |
216 { | |
217 case Orthanc::PixelFormat_RGB24: | |
218 EmitMessage(payload, reader); | |
219 break; | |
220 | |
221 case Orthanc::PixelFormat_Grayscale8: | |
222 { | |
223 const DicomInstanceParameters& parameters = frames_.GetInstanceParameters(payload.GetSeriesIndex()); | |
224 | |
225 Orthanc::Image scaled(parameters.GetExpectedPixelFormat(), reader.GetWidth(), reader.GetHeight(), false); | |
226 Orthanc::ImageProcessing::Convert(scaled, reader); | |
227 | |
228 float w = payload.GetWindowingWidth(); | |
229 if (w <= 0.01f) | |
230 { | |
231 w = 0.01f; // Prevent division by zero | |
232 } | |
233 | |
234 const float c = payload.GetWindowingCenter(); | |
235 const float scaling = w / 255.0f; | |
236 const float offset = (c - w / 2.0f) / scaling; | |
237 | |
238 Orthanc::ImageProcessing::ShiftScale(scaled, offset, scaling, false /* truncation to speed up */); | |
239 EmitMessage(payload, scaled); | |
240 break; | |
241 } | |
242 | |
243 default: | |
244 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
245 } | |
246 } | |
247 | |
248 | |
249 #if ORTHANC_ENABLE_DCMTK == 1 | |
250 void SeriesFramesLoader::Handle(const ParseDicomSuccessMessage& message) | |
251 { | |
252 assert(message.GetOrigin().HasPayload()); | |
253 | |
254 const Payload& payload = dynamic_cast<const Payload&>(message.GetOrigin().GetPayload()); | |
255 if ((payload.GetSource().IsDicomDir() || | |
256 payload.GetSource().IsDicomWeb()) && | |
257 message.HasPixelData()) | |
258 { | |
259 HandleDicom(dynamic_cast<const Payload&>(message.GetOrigin().GetPayload()), message.GetDicom()); | |
260 } | |
261 else | |
262 { | |
263 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
264 } | |
265 } | |
266 #endif | |
267 | |
268 | |
269 void SeriesFramesLoader::Handle(const GetOrthancImageCommand::SuccessMessage& message) | |
270 { | |
271 assert(message.GetOrigin().HasPayload()); | |
272 | |
273 const Payload& payload = dynamic_cast<const Payload&>(message.GetOrigin().GetPayload()); | |
274 assert(payload.GetSource().IsOrthanc()); | |
275 | |
276 EmitMessage(payload, message.GetImage()); | |
277 } | |
278 | |
279 | |
280 void SeriesFramesLoader::Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message) | |
281 { | |
282 assert(message.GetOrigin().HasPayload()); | |
283 | |
284 const Payload& payload = dynamic_cast<const Payload&>(message.GetOrigin().GetPayload()); | |
285 assert(payload.GetSource().IsOrthanc()); | |
286 | |
287 EmitMessage(payload, message.GetImage()); | |
288 } | |
289 | |
290 | |
291 void SeriesFramesLoader::Handle(const OrthancRestApiCommand::SuccessMessage& message) | |
292 { | |
293 // This is to handle "/rendered" in DICOMweb | |
294 assert(message.GetOrigin().HasPayload()); | |
295 HandleDicomWebRendered(dynamic_cast<const Payload&>(message.GetOrigin().GetPayload()), | |
296 message.GetAnswer(), message.GetAnswerHeaders()); | |
297 } | |
298 | |
299 | |
300 void SeriesFramesLoader::Handle(const HttpCommand::SuccessMessage& message) | |
301 { | |
302 // This is to handle "/rendered" in DICOMweb | |
303 assert(message.GetOrigin().HasPayload()); | |
304 HandleDicomWebRendered(dynamic_cast<const Payload&>(message.GetOrigin().GetPayload()), | |
305 message.GetAnswer(), message.GetAnswerHeaders()); | |
306 } | |
307 | |
308 | |
309 void SeriesFramesLoader::GetPreviewWindowing(float& center, | |
310 float& width, | |
311 size_t index) const | |
312 { | |
313 const Orthanc::DicomMap& instance = frames_.GetInstance(index); | |
314 const DicomInstanceParameters& parameters = frames_.GetInstanceParameters(index); | |
315 | |
316 if (parameters.HasDefaultWindowing()) | |
317 { | |
318 // TODO - Handle multiple presets (take the largest width) | |
319 center = parameters.GetDefaultWindowingCenter(); | |
320 width = parameters.GetDefaultWindowingWidth(); | |
321 } | |
322 else | |
323 { | |
324 float a, b; | |
325 if (instance.ParseFloat(a, Orthanc::DICOM_TAG_SMALLEST_IMAGE_PIXEL_VALUE) && | |
326 instance.ParseFloat(b, Orthanc::DICOM_TAG_LARGEST_IMAGE_PIXEL_VALUE) && | |
327 a < b) | |
328 { | |
329 center = (a + b) / 2.0f; | |
330 width = (b - a); | |
331 } | |
332 else | |
333 { | |
334 // Cannot infer a suitable windowing from the available tags | |
335 center = 128.0f; | |
336 width = 256.0f; | |
337 } | |
338 } | |
339 } | |
340 | |
341 | |
342 Orthanc::IDynamicObject& SeriesFramesLoader::FrameLoadedMessage::GetUserPayload() const | |
343 { | |
344 if (userPayload_) | |
345 { | |
346 return *userPayload_; | |
347 } | |
348 else | |
349 { | |
350 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
351 } | |
352 } | |
353 | |
354 | |
355 void SeriesFramesLoader::Factory::SetDicomDir(const std::string& dicomDirPath, | |
356 boost::shared_ptr<LoadedDicomResources> dicomDir) | |
357 { | |
358 dicomDirPath_ = dicomDirPath; | |
359 dicomDir_ = dicomDir; | |
360 } | |
361 | |
362 | |
363 boost::shared_ptr<IObserver> SeriesFramesLoader::Factory::Create(ILoadersContext::ILock& stone) | |
364 { | |
365 boost::shared_ptr<SeriesFramesLoader> loader( | |
366 new SeriesFramesLoader(stone.GetContext(), instances_, dicomDirPath_, dicomDir_)); | |
367 loader->Register<GetOrthancImageCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle); | |
368 loader->Register<GetOrthancWebViewerJpegCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle); | |
369 loader->Register<HttpCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle); | |
370 loader->Register<OrthancRestApiCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle); | |
371 | |
372 #if ORTHANC_ENABLE_DCMTK == 1 | |
373 loader->Register<ParseDicomSuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle); | |
374 #endif | |
375 | |
376 return loader; | |
377 } | |
378 | |
379 | |
380 void SeriesFramesLoader::ScheduleLoadFrame(int priority, | |
381 const DicomSource& source, | |
382 size_t index, | |
383 unsigned int quality, | |
384 Orthanc::IDynamicObject* userPayload) | |
385 { | |
386 std::unique_ptr<Orthanc::IDynamicObject> protection(userPayload); | |
387 | |
388 if (index >= frames_.GetFramesCount() || | |
389 quality >= source.GetQualityCount()) | |
390 { | |
391 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
392 } | |
393 | |
394 const Orthanc::DicomMap& instance = frames_.GetInstance(index); | |
395 | |
396 std::string sopInstanceUid; | |
397 if (!instance.LookupStringValue(sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) | |
398 { | |
399 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, | |
400 "Missing SOPInstanceUID in a DICOM instance"); | |
401 } | |
402 | |
403 if (source.IsDicomDir()) | |
404 { | |
405 if (dicomDir_.get() == NULL) | |
406 { | |
407 // Should have been set in the factory | |
408 throw Orthanc::OrthancException( | |
409 Orthanc::ErrorCode_BadSequenceOfCalls, | |
410 "SeriesFramesLoader::Factory::SetDicomDir() should have been called"); | |
411 } | |
412 | |
413 assert(quality == 0); | |
414 | |
415 std::string file; | |
416 if (dicomDir_->LookupStringValue(file, sopInstanceUid, Orthanc::DICOM_TAG_REFERENCED_FILE_ID)) | |
417 { | |
418 std::unique_ptr<ParseDicomFromFileCommand> command(new ParseDicomFromFileCommand(source, dicomDirPath_, file)); | |
419 command->SetPixelDataIncluded(true); | |
420 command->AcquirePayload(new Payload(source, index, sopInstanceUid, quality, protection.release())); | |
421 | |
422 { | |
423 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); | |
424 lock->Schedule(GetSharedObserver(), priority, command.release()); | |
425 } | |
426 } | |
427 else | |
428 { | |
429 LOG(WARNING) << "Missing tag ReferencedFileID in a DICOMDIR entry"; | |
430 } | |
431 } | |
432 else if (source.IsDicomWeb()) | |
433 { | |
434 std::string studyInstanceUid, seriesInstanceUid; | |
435 if (!instance.LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) || | |
436 !instance.LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false)) | |
437 { | |
438 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, | |
439 "Missing StudyInstanceUID or SeriesInstanceUID in a DICOM instance"); | |
440 } | |
441 | |
442 const std::string uri = ("/studies/" + studyInstanceUid + | |
443 "/series/" + seriesInstanceUid + | |
444 "/instances/" + sopInstanceUid); | |
445 | |
446 if (source.HasDicomWebRendered() && | |
447 quality == 0) | |
448 { | |
449 float c, w; | |
450 GetPreviewWindowing(c, w, index); | |
451 | |
452 std::map<std::string, std::string> arguments, headers; | |
453 arguments["window"] = (boost::lexical_cast<std::string>(c) + "," + | |
454 boost::lexical_cast<std::string>(w) + ",linear"); | |
455 headers["Accept"] = "image/jpeg"; | |
456 | |
457 std::unique_ptr<Payload> payload(new Payload(source, index, sopInstanceUid, quality, protection.release())); | |
458 payload->SetWindowing(c, w); | |
459 | |
460 { | |
461 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); | |
462 lock->Schedule(GetSharedObserver(), priority, | |
463 source.CreateDicomWebCommand(uri + "/rendered", arguments, headers, payload.release())); | |
464 } | |
465 } | |
466 else | |
467 { | |
468 assert((source.HasDicomWebRendered() && quality == 1) || | |
469 (!source.HasDicomWebRendered() && quality == 0)); | |
470 | |
471 #if ORTHANC_ENABLE_DCMTK == 1 | |
472 std::unique_ptr<Payload> payload(new Payload(source, index, sopInstanceUid, quality, protection.release())); | |
473 | |
474 const std::map<std::string, std::string> empty; | |
475 | |
476 std::unique_ptr<ParseDicomFromWadoCommand> command( | |
477 new ParseDicomFromWadoCommand(source, sopInstanceUid, source.CreateDicomWebCommand(uri, empty, empty, NULL))); | |
478 command->AcquirePayload(payload.release()); | |
479 | |
480 { | |
481 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); | |
482 lock->Schedule(GetSharedObserver(), priority, command.release()); | |
483 } | |
484 #else | |
485 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, | |
486 "DCMTK is not enabled, cannot parse a DICOM instance"); | |
487 #endif | |
488 } | |
489 } | |
490 else if (source.IsOrthanc()) | |
491 { | |
492 std::string orthancId; | |
493 | |
494 { | |
495 std::string patientId, studyInstanceUid, seriesInstanceUid; | |
496 if (!instance.LookupStringValue(patientId, Orthanc::DICOM_TAG_PATIENT_ID, false) || | |
497 !instance.LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) || | |
498 !instance.LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false)) | |
499 { | |
500 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, | |
501 "Missing StudyInstanceUID or SeriesInstanceUID in a DICOM instance"); | |
502 } | |
503 | |
504 Orthanc::DicomInstanceHasher hasher(patientId, studyInstanceUid, seriesInstanceUid, sopInstanceUid); | |
505 orthancId = hasher.HashInstance(); | |
506 } | |
507 | |
508 const DicomInstanceParameters& parameters = frames_.GetInstanceParameters(index); | |
509 | |
510 if (quality == 0 && source.HasOrthancWebViewer1()) | |
511 { | |
512 std::unique_ptr<GetOrthancWebViewerJpegCommand> command(new GetOrthancWebViewerJpegCommand); | |
513 command->SetInstance(orthancId); | |
514 command->SetExpectedPixelFormat(parameters.GetExpectedPixelFormat()); | |
515 command->AcquirePayload(new Payload(source, index, sopInstanceUid, quality, protection.release())); | |
516 | |
517 { | |
518 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); | |
519 lock->Schedule(GetSharedObserver(), priority, command.release()); | |
520 } | |
521 } | |
522 else if (quality == 0 && source.HasOrthancAdvancedPreview()) | |
523 { | |
524 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
525 } | |
526 else | |
527 { | |
528 assert(quality <= 1); | |
529 assert(quality == 0 || | |
530 source.HasOrthancWebViewer1() || | |
531 source.HasOrthancAdvancedPreview()); | |
532 | |
533 std::unique_ptr<GetOrthancImageCommand> command(new GetOrthancImageCommand); | |
534 command->SetFrameUri(orthancId, frames_.GetFrameIndex(index), parameters.GetExpectedPixelFormat()); | |
535 command->SetExpectedPixelFormat(parameters.GetExpectedPixelFormat()); | |
536 command->SetHttpHeader("Accept", Orthanc::MIME_PAM); | |
537 command->AcquirePayload(new Payload(source, index, sopInstanceUid, quality, protection.release())); | |
538 | |
539 { | |
540 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); | |
541 lock->Schedule(GetSharedObserver(), priority, command.release()); | |
542 } | |
543 } | |
544 } | |
545 else | |
546 { | |
547 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
548 } | |
549 } | |
550 } |