1 /*=========================================================================
2
3 Library: CTK
4
5 Copyright (c) Kitware Inc.
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11 http://www.apache.org/licenses/LICENSE-2.0.txt
12
13 Unless required by applicable law or agreed to in writing, software
14 distributed under the License is distributed on an "AS IS" BASIS,
15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 See the License for the specific language governing permissions and
17 limitations under the License.
18
19 =========================================================================*/
20
21 #include <iostream>
22
23 // DCMTK includes
24 #include <dcmtk/dcmimgle/dcmimage.h>
25 #include <dcmtk/dcmimage/diregist.h> /* Include color image support */
26
27 // CTK includes
28 #include "ctkLogger.h"
29 #include "ctkQImageView.h"
30
31 // ctkDICOMCore includes
32 #include "ctkDICOMFilterProxyModel.h"
33 #include "ctkDICOMModel.h"
34
35 // ctkDICOMWidgets includex
36 #include "ctkDICOMItemView.h"
37
38 // Qt includes
39 #include <QDebug>
40 #include <QDir>
41 #include <QFile>
42 #include <QHBoxLayout>
43 #include <QKeyEvent>
44 #include <QLabel>
45 #include <QMouseEvent>
46 #include <QPainter>
47 #include <QResizeEvent>
48
49 static ctkLogger logger("org.commontk.DICOM.Widgets.ctkDICOMItemView");
50
51 //--------------------------------------------------------------------------
52 class ctkDICOMItemViewPrivate
53 {
54
55 Q_DECLARE_PUBLIC( ctkDICOMItemView );
56
57 public:
58
59 ctkDICOMItemViewPrivate( ctkDICOMItemView& object );
60
61 QString DatabaseDirectory;
62 QModelIndex CurrentImageIndex;
63 QPoint OldMousePos;
64 double DicomIntensityLevel;
65 double DicomIntensityWindow;
66 bool AutoWindowLevel;
67
68 void init();
69
70 void setImage(const QModelIndex& imageIndex, bool defaultIntensity = true);
71
72 void onPatientModelSelected(const QModelIndex& index);
73 void onStudyModelSelected(const QModelIndex& index);
74 void onSeriesModelSelected(const QModelIndex& index);
75 void onImageModelSelected(const QModelIndex& index);
76
77 protected:
78 ctkDICOMItemView* const q_ptr;
79
80 private:
81 Q_DISABLE_COPY( ctkDICOMItemViewPrivate );
82 };
83
84 //--------------------------------------------------------------------------
ctkDICOMItemViewPrivate(ctkDICOMItemView & object)85 ctkDICOMItemViewPrivate::ctkDICOMItemViewPrivate(
86 ctkDICOMItemView& object )
87 : q_ptr( & object )
88 {
89 }
90
91 //--------------------------------------------------------------------------
init()92 void ctkDICOMItemViewPrivate::init()
93 {
94 Q_Q( ctkDICOMItemView );
95
96 q->setMouseTracking(true);
97
98 this->DicomIntensityLevel = 0;
99
100 this->DicomIntensityWindow = 0;
101
102 this->AutoWindowLevel = true;
103
104 /*
105 this->Window->setParent(q);
106 QHBoxLayout* layout = new QHBoxLayout(q);
107 layout->addWidget(this->Window);
108 layout->setContentsMargins(0,0,0,0);
109 q->setLayout(layout);
110 */
111 }
112
113 // -------------------------------------------------------------------------
setImage(const QModelIndex & imageIndex,bool defaultIntensity)114 void ctkDICOMItemViewPrivate::setImage(const QModelIndex &imageIndex, bool defaultIntensity){
115 Q_Q(ctkDICOMItemView);
116
117 ctkDICOMModel* model = const_cast<ctkDICOMModel*>(qobject_cast<const ctkDICOMModel*>(imageIndex.model()));
118
119 if(model){
120 QModelIndex seriesIndex = imageIndex.parent();
121 QModelIndex studyIndex = seriesIndex.parent();
122
123 QString dicomPath = this->DatabaseDirectory;
124 dicomPath.append("/dicom/").append(model->data(studyIndex ,ctkDICOMModel::UIDRole).toString());
125 dicomPath.append("/").append(model->data(seriesIndex ,ctkDICOMModel::UIDRole).toString());
126 dicomPath.append("/").append(model->data(imageIndex ,ctkDICOMModel::UIDRole).toString());
127
128 if (QFile(dicomPath).exists()){
129 DicomImage dcmImage( QDir::toNativeSeparators(dicomPath).toStdString().c_str() );
130
131 q->clearImages();
132 q->addImage(dcmImage, defaultIntensity);
133 this->CurrentImageIndex = imageIndex;
134
135 q->emitImageDisplayedSignal(imageIndex.row(), model->rowCount(seriesIndex));
136 }else{
137 q->clearImages();
138 }
139 }
140 }
141
142 // -------------------------------------------------------------------------
onPatientModelSelected(const QModelIndex & index)143 void ctkDICOMItemViewPrivate::onPatientModelSelected(const QModelIndex &index){
144 Q_Q(ctkDICOMItemView);
145
146 ctkDICOMModel* model = const_cast<ctkDICOMModel*>(qobject_cast<const ctkDICOMModel*>(index.model()));
147
148 if(model){
149 QModelIndex patientIndex = index;
150 model->fetchMore(patientIndex);
151 QModelIndex studyIndex = patientIndex.child(0,0);
152 model->fetchMore(studyIndex);
153 QModelIndex seriesIndex = studyIndex.child(0,0);
154 model->fetchMore(seriesIndex);
155 int imageCount = model->rowCount(seriesIndex);
156 QModelIndex imageIndex = seriesIndex.child(imageCount/2,0);
157
158 this->setImage(imageIndex);
159 }else{
160 q->clearImages();
161 }
162 }
163
164 // -------------------------------------------------------------------------
onStudyModelSelected(const QModelIndex & index)165 void ctkDICOMItemViewPrivate::onStudyModelSelected(const QModelIndex &index){
166 Q_Q(ctkDICOMItemView);
167
168 ctkDICOMModel* model = const_cast<ctkDICOMModel*>(qobject_cast<const ctkDICOMModel*>(index.model()));
169
170 if(model){
171 QModelIndex studyIndex = index;
172 model->fetchMore(studyIndex);
173 QModelIndex seriesIndex = studyIndex.child(0,0);
174 model->fetchMore(seriesIndex);
175 int imageCount = model->rowCount(seriesIndex);
176 QModelIndex imageIndex = seriesIndex.child(imageCount/2,0);
177
178 this->setImage(imageIndex);
179 }else{
180 q->clearImages();
181 }
182 }
183
184 // -------------------------------------------------------------------------
onSeriesModelSelected(const QModelIndex & index)185 void ctkDICOMItemViewPrivate::onSeriesModelSelected(const QModelIndex &index){
186 Q_Q(ctkDICOMItemView);
187
188 ctkDICOMModel* model = const_cast<ctkDICOMModel*>(qobject_cast<const ctkDICOMModel*>(index.model()));
189
190 if(model){
191 QModelIndex seriesIndex = index;
192 model->fetchMore(seriesIndex);
193 int imageCount = model->rowCount(seriesIndex);
194 QModelIndex imageIndex = seriesIndex.child(imageCount/2,0);
195
196 this->setImage(imageIndex);
197 }else{
198 q->clearImages();
199 }
200 }
201
202 // -------------------------------------------------------------------------
onImageModelSelected(const QModelIndex & index)203 void ctkDICOMItemViewPrivate::onImageModelSelected(const QModelIndex &index){
204 Q_Q(ctkDICOMItemView);
205
206 ctkDICOMModel* model = const_cast<ctkDICOMModel*>(qobject_cast<const ctkDICOMModel*>(index.model()));
207
208 if(model){
209 QModelIndex imageIndex = index;
210
211 if(index.parent() == this->CurrentImageIndex.parent()){
212 this->setImage(imageIndex, false);
213 }else{
214 this->setImage(imageIndex, true);
215 }
216 }else{
217 q->clearImages();
218 }
219 }
220
221 // -------------------------------------------------------------------------
ctkDICOMItemView(QWidget * _parent)222 ctkDICOMItemView::ctkDICOMItemView( QWidget* _parent )
223 : Superclass( _parent ),
224 d_ptr( new ctkDICOMItemViewPrivate( *this ) )
225 {
226 Q_D( ctkDICOMItemView );
227 d->init();
228 }
229
230 // -------------------------------------------------------------------------
ctkDICOMItemView(ctkDICOMItemViewPrivate & pvt,QWidget * _parent)231 ctkDICOMItemView::ctkDICOMItemView(
232 ctkDICOMItemViewPrivate& pvt,
233 QWidget* _parent)
234 : Superclass(_parent), d_ptr(&pvt)
235 {
236 Q_D(ctkDICOMItemView);
237 d->init();
238 }
239
240 // -------------------------------------------------------------------------
~ctkDICOMItemView()241 ctkDICOMItemView::~ctkDICOMItemView()
242 {
243 }
244
245 // -------------------------------------------------------------------------
setDatabaseDirectory(const QString & directory)246 void ctkDICOMItemView::setDatabaseDirectory(const QString &directory){
247 Q_D(ctkDICOMItemView);
248
249 d->DatabaseDirectory = directory;
250 }
251
252 // -------------------------------------------------------------------------
currentImageIndex()253 QModelIndex ctkDICOMItemView::currentImageIndex(){
254 Q_D(ctkDICOMItemView);
255
256 return d->CurrentImageIndex;
257 }
258
259 // -------------------------------------------------------------------------
addImage(const QImage & image)260 void ctkDICOMItemView::addImage( const QImage & image )
261 {
262 Superclass::addImage( image );
263 }
264
265 // -------------------------------------------------------------------------
addImage(DicomImage & dcmImage,bool defaultIntensity)266 void ctkDICOMItemView::addImage( DicomImage & dcmImage, bool defaultIntensity )
267 {
268 Q_D(ctkDICOMItemView);
269 QImage image;
270 // Check whether we have a valid image
271 EI_Status result = dcmImage.getStatus();
272 if (result != EIS_Normal)
273 {
274 logger.error(QString("Rendering of DICOM image failed for thumbnail failed: ") + DicomImage::getString(result));
275 return;
276 }
277 // Select first window defined in image. If none, compute min/max window as best guess.
278 // Only relevant for monochrome
279 if (d->AutoWindowLevel)
280 {
281 if (dcmImage.isMonochrome())
282 {
283 if (defaultIntensity && dcmImage.getWindowCount() > 0)
284 {
285 dcmImage.setWindow(0);
286 }
287 else
288 {
289 dcmImage.setMinMaxWindow(OFTrue /* ignore extreme values */);
290 dcmImage.getWindow(d->DicomIntensityLevel, d->DicomIntensityWindow);
291 }
292 }
293 }
294 else
295 {
296 dcmImage.setWindow(d->DicomIntensityLevel, d->DicomIntensityWindow);
297 }
298 /* get image extension and prepare image header */
299 const unsigned long width = dcmImage.getWidth();
300 const unsigned long height = dcmImage.getHeight();
301 unsigned long offset = 0;
302 unsigned long length = 0;
303 QString header;
304
305 if (dcmImage.isMonochrome())
306 {
307 // write PGM header (binary monochrome image format)
308 header = QString("P5 %1 %2 255\n").arg(width).arg(height);
309 offset = header.length();
310 length = width * height + offset;
311 }
312 else
313 {
314 // write PPM header (binary color image format)
315 header = QString("P6 %1 %2 255\n").arg(width).arg(height);
316 offset = header.length();
317 length = width * height * 3 /* RGB */ + offset;
318 }
319 /* create output buffer for DicomImage class */
320 QByteArray buffer;
321 /* copy header to output buffer and resize it for pixel data */
322 buffer.append(header);
323 buffer.resize(length);
324
325 /* render pixel data to buffer */
326 if (dcmImage.getOutputData(static_cast<void *>(buffer.data() + offset), length - offset, 8, 0))
327 {
328 if (!image.loadFromData( buffer ))
329 {
330 logger.error("QImage couldn't created");
331 }
332 }
333 this->addImage(image);
334 }
335
336 // -------------------------------------------------------------------------
update(bool zoomChanged,bool sizeChanged)337 void ctkDICOMItemView::update( bool zoomChanged,
338 bool sizeChanged )
339 {
340 Superclass::update( zoomChanged, sizeChanged );
341 }
342
343 // -------------------------------------------------------------------------
mousePressEvent(QMouseEvent * event)344 void ctkDICOMItemView::mousePressEvent(QMouseEvent* event){
345 Q_D(ctkDICOMItemView);
346
347 event->accept();
348 d->OldMousePos = event->pos();
349 }
350
351 // -------------------------------------------------------------------------
mouseMoveEvent(QMouseEvent * event)352 void ctkDICOMItemView::mouseMoveEvent(QMouseEvent* event){
353 Q_D(ctkDICOMItemView);
354
355 if(event->buttons() == Qt::RightButton){
356 event->accept();
357 QPoint nowPos = event->pos();
358 if(nowPos.y() > d->OldMousePos.y()){
359 emit requestNextImage();
360 d->OldMousePos = event->pos();
361 }else if(nowPos.y() < d->OldMousePos.y()){
362 emit requestPreviousImage();
363 d->OldMousePos = event->pos();
364 }
365 }else if(event->buttons() == Qt::MidButton){
366 event->accept();
367 QPoint nowPos = event->pos();
368
369 this->setZoom(this->zoom() - (nowPos.y()-d->OldMousePos.y())/100.0);
370
371 d->OldMousePos = event->pos();
372 }else if(event->buttons() == Qt::LeftButton){
373 event->accept();
374 QPoint nowPos = event->pos();
375
376 d->DicomIntensityWindow += (5*(nowPos.x()-d->OldMousePos.x()));
377 d->DicomIntensityLevel -= (5*(nowPos.y()-d->OldMousePos.y()));
378 d->AutoWindowLevel = false;
379
380 d->setImage(d->CurrentImageIndex, false);
381
382 d->OldMousePos = event->pos();
383 }
384
385 Superclass::mouseMoveEvent(event);
386 }
387
388 // -------------------------------------------------------------------------
onModelSelected(const QModelIndex & index)389 void ctkDICOMItemView::onModelSelected(const QModelIndex &index){
390 Q_D(ctkDICOMItemView);
391
392 ctkDICOMModel* model = const_cast<ctkDICOMModel*>(qobject_cast<const ctkDICOMModel*>(index.model()));
393
394 if(model){
395 QModelIndex index0 = index.sibling(index.row(), 0);
396
397 if ( model->data(index0,ctkDICOMModel::TypeRole) == static_cast<int>(ctkDICOMModel::PatientType) ){
398 d->onPatientModelSelected(index0);
399 }else if ( model->data(index0,ctkDICOMModel::TypeRole) == static_cast<int>(ctkDICOMModel::StudyType) ){
400 d->onStudyModelSelected(index0);
401 }else if ( model->data(index0,ctkDICOMModel::TypeRole) == static_cast<int>(ctkDICOMModel::SeriesType) ){
402 d->onSeriesModelSelected(index0);
403 }else if ( model->data(index0,ctkDICOMModel::TypeRole) == static_cast<int>(ctkDICOMModel::ImageType) ){
404 d->onImageModelSelected(index0);
405 }
406 }
407 }
408
409 // -------------------------------------------------------------------------
displayImage(int imageIndex)410 void ctkDICOMItemView::displayImage(int imageIndex){
411 Q_D(ctkDICOMItemView);
412
413 if(d->CurrentImageIndex.isValid())
414 {
415 QModelIndex seriesIndex = d->CurrentImageIndex.parent();
416 ctkDICOMModel* model = const_cast<ctkDICOMModel*>(qobject_cast<const ctkDICOMModel*>(seriesIndex.model()));
417
418 if(model)
419 {
420 if(imageIndex >= 0 && imageIndex < model->rowCount(seriesIndex))
421 {
422 this->onModelSelected(model->index(imageIndex, 0, seriesIndex));
423 }
424 else
425 {
426 logger.debug("out of index");
427 }
428 }
429 }
430 }
431
432 // -------------------------------------------------------------------------
emitImageDisplayedSignal(int imageID,int count)433 void ctkDICOMItemView::emitImageDisplayedSignal(int imageID, int count){
434 emit imageDisplayed(imageID, count);
435 }
436
437