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