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 }