comparison Framework/Loaders/SeriesFramesLoader.cpp @ 1228:c471a0aa137b broker

adding the next generation of loaders
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 09 Dec 2019 13:58:37 +0100
parents
children 0ca50d275b9a
comparison
equal deleted inserted replaced
1227:a1c0c9c9f9af 1228:c471a0aa137b
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 "SeriesFramesLoader.h"
23
24 #include "../Oracle/ParseDicomFromFileCommand.h"
25 #include "../Oracle/ParseDicomFromWadoCommand.h"
26
27 #if ORTHANC_ENABLE_DCMTK == 1
28 # include <Core/DicomParsing/Internals/DicomImageDecoder.h>
29 #endif
30
31 #include <Core/DicomFormat/DicomInstanceHasher.h>
32 #include <Core/Images/Image.h>
33 #include <Core/Images/ImageProcessing.h>
34 #include <Core/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::auto_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::auto_ptr<Orthanc::ImageAccessor> decoded;
173 decoded.reset(Orthanc::DicomImageDecoder::Decode(dicom, frameIndex));
174
175 if (decoded.get() == NULL)
176 {
177 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
178 }
179
180 EmitMessage(payload, *decoded);
181 }
182 #endif
183
184
185 void SeriesFramesLoader::HandleDicomWebRendered(const Payload& payload,
186 const std::string& body,
187 const std::map<std::string, std::string>& headers)
188 {
189 assert(payload.GetSource().IsDicomWeb() &&
190 payload.HasWindowing());
191
192 bool ok = false;
193 for (std::map<std::string, std::string>::const_iterator it = headers.begin();
194 it != headers.end(); ++it)
195 {
196 if (boost::iequals("content-type", it->first) &&
197 boost::iequals(Orthanc::MIME_JPEG, it->second))
198 {
199 ok = true;
200 break;
201 }
202 }
203
204 if (!ok)
205 {
206 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
207 "The WADO-RS server has not generated a JPEG image on /rendered");
208 }
209
210 Orthanc::JpegReader reader;
211 reader.ReadFromMemory(body);
212
213 switch (reader.GetFormat())
214 {
215 case Orthanc::PixelFormat_RGB24:
216 EmitMessage(payload, reader);
217 break;
218
219 case Orthanc::PixelFormat_Grayscale8:
220 {
221 const DicomInstanceParameters& parameters = frames_.GetInstanceParameters(payload.GetSeriesIndex());
222
223 Orthanc::Image scaled(parameters.GetExpectedPixelFormat(), reader.GetWidth(), reader.GetHeight(), false);
224 Orthanc::ImageProcessing::Convert(scaled, reader);
225
226 float w = payload.GetWindowingWidth();
227 if (w <= 0.01f)
228 {
229 w = 0.01; // Prevent division by zero
230 }
231
232 const float c = payload.GetWindowingCenter();
233 const float scaling = w / 255.0f;
234 const float offset = (c - w / 2.0f) / scaling;
235
236 Orthanc::ImageProcessing::ShiftScale(scaled, offset, scaling, false /* truncation to speed up */);
237 EmitMessage(payload, scaled);
238 break;
239 }
240
241 default:
242 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
243 }
244 }
245
246
247 #if ORTHANC_ENABLE_DCMTK == 1
248 void SeriesFramesLoader::Handle(const ParseDicomSuccessMessage& message)
249 {
250 assert(message.GetOrigin().HasPayload());
251
252 const Payload& payload = dynamic_cast<const Payload&>(message.GetOrigin().GetPayload());
253 if ((payload.GetSource().IsDicomDir() ||
254 payload.GetSource().IsDicomWeb()) &&
255 message.HasPixelData())
256 {
257 HandleDicom(dynamic_cast<const Payload&>(message.GetOrigin().GetPayload()), message.GetDicom());
258 }
259 else
260 {
261 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
262 }
263 }
264 #endif
265
266
267 void SeriesFramesLoader::Handle(const GetOrthancImageCommand::SuccessMessage& message)
268 {
269 assert(message.GetOrigin().HasPayload());
270
271 const Payload& payload = dynamic_cast<const Payload&>(message.GetOrigin().GetPayload());
272 assert(payload.GetSource().IsOrthanc());
273
274 EmitMessage(payload, message.GetImage());
275 }
276
277
278 void SeriesFramesLoader::Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message)
279 {
280 assert(message.GetOrigin().HasPayload());
281
282 const Payload& payload = dynamic_cast<const Payload&>(message.GetOrigin().GetPayload());
283 assert(payload.GetSource().IsOrthanc());
284
285 EmitMessage(payload, message.GetImage());
286 }
287
288
289 void SeriesFramesLoader::Handle(const OrthancRestApiCommand::SuccessMessage& message)
290 {
291 // This is to handle "/rendered" in DICOMweb
292 assert(message.GetOrigin().HasPayload());
293 HandleDicomWebRendered(dynamic_cast<const Payload&>(message.GetOrigin().GetPayload()),
294 message.GetAnswer(), message.GetAnswerHeaders());
295 }
296
297
298 void SeriesFramesLoader::Handle(const HttpCommand::SuccessMessage& message)
299 {
300 // This is to handle "/rendered" in DICOMweb
301 assert(message.GetOrigin().HasPayload());
302 HandleDicomWebRendered(dynamic_cast<const Payload&>(message.GetOrigin().GetPayload()),
303 message.GetAnswer(), message.GetAnswerHeaders());
304 }
305
306
307 void SeriesFramesLoader::GetPreviewWindowing(float& center,
308 float& width,
309 size_t index) const
310 {
311 const Orthanc::DicomMap& instance = frames_.GetInstance(index);
312 const DicomInstanceParameters& parameters = frames_.GetInstanceParameters(index);
313
314 if (parameters.HasDefaultWindowing())
315 {
316 // TODO - Handle multiple presets (take the largest width)
317 center = parameters.GetDefaultWindowingCenter();
318 width = parameters.GetDefaultWindowingWidth();
319 }
320 else
321 {
322 float a, b;
323 if (instance.ParseFloat(a, Orthanc::DICOM_TAG_SMALLEST_IMAGE_PIXEL_VALUE) &&
324 instance.ParseFloat(b, Orthanc::DICOM_TAG_LARGEST_IMAGE_PIXEL_VALUE) &&
325 a < b)
326 {
327 center = (a + b) / 2.0f;
328 width = (b - a);
329 }
330 else
331 {
332 // Cannot infer a suitable windowing from the available tags
333 center = 128.0f;
334 width = 256.0f;
335 }
336 }
337 }
338
339
340 Orthanc::IDynamicObject& SeriesFramesLoader::FrameLoadedMessage::GetUserPayload() const
341 {
342 if (userPayload_)
343 {
344 return *userPayload_;
345 }
346 else
347 {
348 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
349 }
350 }
351
352
353 void SeriesFramesLoader::Factory::SetDicomDir(const std::string& dicomDirPath,
354 boost::shared_ptr<LoadedDicomResources> dicomDir)
355 {
356 dicomDirPath_ = dicomDirPath;
357 dicomDir_ = dicomDir;
358 }
359
360
361 boost::shared_ptr<IObserver> SeriesFramesLoader::Factory::Create(ILoadersContext::ILock& stone)
362 {
363 boost::shared_ptr<SeriesFramesLoader> loader(
364 new SeriesFramesLoader(stone.GetContext(), instances_, dicomDirPath_, dicomDir_));
365 loader->Register<GetOrthancImageCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle);
366 loader->Register<GetOrthancWebViewerJpegCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle);
367 loader->Register<HttpCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle);
368 loader->Register<OrthancRestApiCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle);
369
370 #if ORTHANC_ENABLE_DCMTK == 1
371 loader->Register<ParseDicomSuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle);
372 #endif
373
374 return loader;
375 }
376
377
378 void SeriesFramesLoader::ScheduleLoadFrame(int priority,
379 const DicomSource& source,
380 size_t index,
381 unsigned int quality,
382 Orthanc::IDynamicObject* userPayload)
383 {
384 std::auto_ptr<Orthanc::IDynamicObject> protection(userPayload);
385
386 if (index >= frames_.GetFramesCount() ||
387 quality >= source.GetQualityCount())
388 {
389 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
390 }
391
392 const Orthanc::DicomMap& instance = frames_.GetInstance(index);
393
394 std::string sopInstanceUid;
395 if (!instance.LookupStringValue(sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false))
396 {
397 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
398 "Missing SOPInstanceUID in a DICOM instance");
399 }
400
401 if (source.IsDicomDir())
402 {
403 if (dicomDir_.get() == NULL)
404 {
405 // Should have been set in the factory
406 throw Orthanc::OrthancException(
407 Orthanc::ErrorCode_BadSequenceOfCalls,
408 "SeriesFramesLoader::Factory::SetDicomDir() should have been called");
409 }
410
411 assert(quality == 0);
412
413 std::string file;
414 if (dicomDir_->LookupStringValue(file, sopInstanceUid, Orthanc::DICOM_TAG_REFERENCED_FILE_ID))
415 {
416 std::auto_ptr<ParseDicomFromFileCommand> command(new ParseDicomFromFileCommand(dicomDirPath_, file));
417 command->SetPixelDataIncluded(true);
418 command->AcquirePayload(new Payload(source, index, sopInstanceUid, quality, protection.release()));
419
420 {
421 std::auto_ptr<ILoadersContext::ILock> lock(context_.Lock());
422 lock->Schedule(GetSharedObserver(), priority, command.release());
423 }
424 }
425 else
426 {
427 LOG(WARNING) << "Missing tag ReferencedFileID in a DICOMDIR entry";
428 }
429 }
430 else if (source.IsDicomWeb())
431 {
432 std::string studyInstanceUid, seriesInstanceUid;
433 if (!instance.LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) ||
434 !instance.LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false))
435 {
436 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
437 "Missing StudyInstanceUID or SeriesInstanceUID in a DICOM instance");
438 }
439
440 const std::string uri = ("/studies/" + studyInstanceUid +
441 "/series/" + seriesInstanceUid +
442 "/instances/" + sopInstanceUid);
443
444 if (source.HasDicomWebRendered() &&
445 quality == 0)
446 {
447 float c, w;
448 GetPreviewWindowing(c, w, index);
449
450 std::map<std::string, std::string> arguments, headers;
451 arguments["window"] = (boost::lexical_cast<std::string>(c) + "," +
452 boost::lexical_cast<std::string>(w) + ",linear");
453 headers["Accept"] = "image/jpeg";
454
455 std::auto_ptr<Payload> payload(new Payload(source, index, sopInstanceUid, quality, protection.release()));
456 payload->SetWindowing(c, w);
457
458 {
459 std::auto_ptr<ILoadersContext::ILock> lock(context_.Lock());
460 lock->Schedule(GetSharedObserver(), priority,
461 source.CreateDicomWebCommand(uri + "/rendered", arguments, headers, payload.release()));
462 }
463 }
464 else
465 {
466 assert((source.HasDicomWebRendered() && quality == 1) ||
467 (!source.HasDicomWebRendered() && quality == 0));
468
469 #if ORTHANC_ENABLE_DCMTK == 1
470 std::auto_ptr<Payload> payload(new Payload(source, index, sopInstanceUid, quality, protection.release()));
471
472 const std::map<std::string, std::string> empty;
473
474 std::auto_ptr<ParseDicomFromWadoCommand> command(
475 new ParseDicomFromWadoCommand(sopInstanceUid, source.CreateDicomWebCommand(uri, empty, empty, NULL)));
476 command->AcquirePayload(payload.release());
477
478 {
479 std::auto_ptr<ILoadersContext::ILock> lock(context_.Lock());
480 lock->Schedule(GetSharedObserver(), priority, command.release());
481 }
482 #else
483 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
484 "DCMTK is not enabled, cannot parse a DICOM instance");
485 #endif
486 }
487 }
488 else if (source.IsOrthanc())
489 {
490 std::string orthancId;
491
492 {
493 std::string patientId, studyInstanceUid, seriesInstanceUid;
494 if (!instance.LookupStringValue(patientId, Orthanc::DICOM_TAG_PATIENT_ID, false) ||
495 !instance.LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) ||
496 !instance.LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false))
497 {
498 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
499 "Missing StudyInstanceUID or SeriesInstanceUID in a DICOM instance");
500 }
501
502 Orthanc::DicomInstanceHasher hasher(patientId, studyInstanceUid, seriesInstanceUid, sopInstanceUid);
503 orthancId = hasher.HashInstance();
504 }
505
506 const DicomInstanceParameters& parameters = frames_.GetInstanceParameters(index);
507
508 if (quality == 0 && source.HasOrthancWebViewer1())
509 {
510 std::auto_ptr<GetOrthancWebViewerJpegCommand> command(new GetOrthancWebViewerJpegCommand);
511 command->SetInstance(orthancId);
512 command->SetExpectedPixelFormat(parameters.GetExpectedPixelFormat());
513 command->AcquirePayload(new Payload(source, index, sopInstanceUid, quality, protection.release()));
514
515 {
516 std::auto_ptr<ILoadersContext::ILock> lock(context_.Lock());
517 lock->Schedule(GetSharedObserver(), priority, command.release());
518 }
519 }
520 else if (quality == 0 && source.HasOrthancAdvancedPreview())
521 {
522 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
523 }
524 else
525 {
526 assert(quality <= 1);
527 assert(quality == 0 ||
528 source.HasOrthancWebViewer1() ||
529 source.HasOrthancAdvancedPreview());
530
531 std::auto_ptr<GetOrthancImageCommand> command(new GetOrthancImageCommand);
532 command->SetFrameUri(orthancId, frames_.GetFrameIndex(index), parameters.GetExpectedPixelFormat());
533 command->SetExpectedPixelFormat(parameters.GetExpectedPixelFormat());
534 command->SetHttpHeader("Accept", Orthanc::MIME_PAM);
535 command->AcquirePayload(new Payload(source, index, sopInstanceUid, quality, protection.release()));
536
537 {
538 std::auto_ptr<ILoadersContext::ILock> lock(context_.Lock());
539 lock->Schedule(GetSharedObserver(), priority, command.release());
540 }
541 }
542 }
543 else
544 {
545 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
546 }
547 }
548 }