From efe6d8cf3003523597aa12e0add6c02c58830195 Mon Sep 17 00:00:00 2001 From: Pierre Bour Date: Wed, 25 Aug 2021 21:27:34 +0200 Subject: [PATCH] ENH: Improve DICOM query/retrieve widget * Retrieve only selected series (previously, all series of the selected studies were retrieved) * Add collapsible button to hide query or retrieve section, to give more space for browsing query results --- .../Core/Testing/Cpp/ctkDICOMQueryTest1.cpp | 6 +- .../Core/Testing/Cpp/ctkDICOMQueryTest2.cpp | 2 +- .../Testing/Cpp/ctkDICOMRetrieveTest2.cpp | 12 +- Libs/DICOM/Core/ctkDICOMQuery.cpp | 38 +++-- Libs/DICOM/Core/ctkDICOMQuery.h | 6 +- .../UI/ctkDICOMQueryRetrieveWidget.ui | 139 ++++++++++-------- .../Widgets/ctkDICOMQueryRetrieveWidget.cpp | 56 +++++-- .../Widgets/ctkDICOMQueryRetrieveWidget.h | 2 + 8 files changed, 162 insertions(+), 99 deletions(-) diff --git a/Libs/DICOM/Core/Testing/Cpp/ctkDICOMQueryTest1.cpp b/Libs/DICOM/Core/Testing/Cpp/ctkDICOMQueryTest1.cpp index 14fe9ee60c..6635e7bf0c 100644 --- a/Libs/DICOM/Core/Testing/Cpp/ctkDICOMQueryTest1.cpp +++ b/Libs/DICOM/Core/Testing/Cpp/ctkDICOMQueryTest1.cpp @@ -43,7 +43,7 @@ int ctkDICOMQueryTest1( int argc, char * argv [] ) !query.host().isEmpty() || query.port() != 0 || !query.filters().isEmpty() || - !query.studyInstanceUIDQueried().isEmpty()) + !query.studyAndSeriesInstanceUIDQueried().isEmpty()) { std::cerr << "ctkDICOMQuery::ctkDICOMQuery() failed: " << qPrintable(query.callingAETitle()) << " " @@ -102,10 +102,10 @@ int ctkDICOMQueryTest1( int argc, char * argv [] ) query.query(database); // Queried studies should be empty because we use an empty database. - if (!query.studyInstanceUIDQueried().isEmpty()) + if (!query.studyAndSeriesInstanceUIDQueried().isEmpty()) { std::cerr << "ctkDICOMDatabase::query() failed: " - << query.studyInstanceUIDQueried().count() << std::endl; + << query.studyAndSeriesInstanceUIDQueried().count() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; diff --git a/Libs/DICOM/Core/Testing/Cpp/ctkDICOMQueryTest2.cpp b/Libs/DICOM/Core/Testing/Cpp/ctkDICOMQueryTest2.cpp index d7652f9c65..f797eca089 100644 --- a/Libs/DICOM/Core/Testing/Cpp/ctkDICOMQueryTest2.cpp +++ b/Libs/DICOM/Core/Testing/Cpp/ctkDICOMQueryTest2.cpp @@ -69,7 +69,7 @@ int ctkDICOMQueryTest2( int argc, char * argv [] ) std::cout << "ctkDICOMQuery::query() failed" << std::endl; return EXIT_FAILURE; } - if (query.studyInstanceUIDQueried().count() == 0) + if (query.studyAndSeriesInstanceUIDQueried().count() == 0) { std::cout << "ctkDICOMQuery::query() failed." << "No study instance retrieved" << std::endl; diff --git a/Libs/DICOM/Core/Testing/Cpp/ctkDICOMRetrieveTest2.cpp b/Libs/DICOM/Core/Testing/Cpp/ctkDICOMRetrieveTest2.cpp index 6d7b2ee427..bb3ae2be24 100644 --- a/Libs/DICOM/Core/Testing/Cpp/ctkDICOMRetrieveTest2.cpp +++ b/Libs/DICOM/Core/Testing/Cpp/ctkDICOMRetrieveTest2.cpp @@ -21,6 +21,8 @@ // Qt includes #include #include +#include +#include #include #include @@ -74,7 +76,7 @@ int ctkDICOMRetrieveTest2( int argc, char * argv [] ) std::cout << "ctkDICOMQuery::query() failed" << std::endl; return EXIT_FAILURE; } - if (query.studyInstanceUIDQueried().count() == 0) + if (query.studyAndSeriesInstanceUIDQueried().count() == 0) { std::cout << "ctkDICOMQuery::query() failed." << "No study instance retrieved" << std::endl; @@ -95,14 +97,14 @@ int ctkDICOMRetrieveTest2( int argc, char * argv [] ) retrieve.setDatabase(retrieveDatabase); std::cerr << "ctkDICOMRetrieveTest2: Retrieving\n"; - foreach(const QString& study, query.studyInstanceUIDQueried()) + foreach(auto studyAndSeriesInstanceUID, query.studyAndSeriesInstanceUIDQueried()) { - std::cerr << "ctkDICOMRetrieveTest2: Retrieving " << study.toStdString() << "\n"; - bool res = retrieve.moveStudy(study); + std::cerr << "ctkDICOMRetrieveTest2: Retrieving " << studyAndSeriesInstanceUID.first.toStdString() << "\n"; + bool res = retrieve.moveStudy(studyAndSeriesInstanceUID.first); if (!res) { std::cout << "ctkDICOMRetrieve::retrieveStudy() failed. " - << "Study " << qPrintable(study) << " can't be retrieved" + << "Study " << qPrintable(studyAndSeriesInstanceUID.first) << " can't be retrieved" << std::endl; return EXIT_FAILURE; } diff --git a/Libs/DICOM/Core/ctkDICOMQuery.cpp b/Libs/DICOM/Core/ctkDICOMQuery.cpp index 8e980a2e57..0792353cda 100644 --- a/Libs/DICOM/Core/ctkDICOMQuery.cpp +++ b/Libs/DICOM/Core/ctkDICOMQuery.cpp @@ -19,16 +19,17 @@ =========================================================================*/ // Qt includes -#include -#include -#include +#include #include -#include -#include -#include #include +#include #include -#include +#include +#include +#include +#include +#include +#include // ctkDICOMCore includes #include "ctkDICOMQuery.h" @@ -88,6 +89,8 @@ class ctkDICOMQueryPrivate /// Add a StudyInstanceUID to be queried void addStudyInstanceUIDAndDataset(const QString& StudyInstanceUID, DcmDataset* dataset ); + /// Add StudyInstanceUID and SeriesInstanceUID that may be further retrieved + void addStudyAndSeriesInstanceUID( const QString& StudyInstanceUID, const QString& SeriesInstanceUID ); QString CallingAETitle; QString CalledAETitle; @@ -97,6 +100,7 @@ class ctkDICOMQueryPrivate QMap Filters; ctkDICOMQuerySCUPrivate SCU; DcmDataset* Query; + QList> StudyAndSeriesInstanceUIDPairList; QStringList StudyInstanceUIDList; QList StudyDatasetList; bool Canceled; @@ -121,9 +125,15 @@ ctkDICOMQueryPrivate::~ctkDICOMQueryPrivate() } //------------------------------------------------------------------------------ -void ctkDICOMQueryPrivate::addStudyInstanceUIDAndDataset( const QString& s, DcmDataset* dataset ) +void ctkDICOMQueryPrivate::addStudyAndSeriesInstanceUID( const QString& study, const QString& series ) +{ + this->StudyAndSeriesInstanceUIDPairList.push_back (qMakePair( study, series ) ); +} + +//------------------------------------------------------------------------------ +void ctkDICOMQueryPrivate::addStudyInstanceUIDAndDataset( const QString& study, DcmDataset* dataset ) { - this->StudyInstanceUIDList.append ( s ); + this->StudyInstanceUIDList.append ( study ); this->StudyDatasetList.append ( dataset ); } @@ -230,10 +240,10 @@ QMap ctkDICOMQuery::filters()const } //------------------------------------------------------------------------------ -QStringList ctkDICOMQuery::studyInstanceUIDQueried()const +QList> ctkDICOMQuery::studyAndSeriesInstanceUIDQueried()const { Q_D(const ctkDICOMQuery); - return d->StudyInstanceUIDList; + return d->StudyAndSeriesInstanceUIDPairList; } //------------------------------------------------------------------------------ @@ -260,6 +270,7 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database ) emit progress(0); if (d->Canceled) {return false;} + d->StudyAndSeriesInstanceUIDPairList.clear(); d->StudyInstanceUIDList.clear(); d->SCU.setAETitle ( OFString(this->callingAETitle().toStdString().c_str()) ); d->SCU.setPeerAETitle ( OFString(this->calledAETitle().toStdString().c_str()) ); @@ -453,7 +464,7 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database ) int i = 0; QListIterator datasetIterator(d->StudyDatasetList); - foreach ( QString StudyInstanceUID, d->StudyInstanceUIDList ) + for (const auto & StudyInstanceUID : d->StudyInstanceUIDList ) { DcmDataset *studyDataset = datasetIterator.next(); DcmElement *patientName, *patientID; @@ -475,6 +486,9 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database ) DcmDataset *dataset = (*it)->m_dataset; if ( dataset != NULL ) { + OFString SeriesInstanceUID; + dataset->findAndGetOFString ( DCM_SeriesInstanceUID, SeriesInstanceUID ); + d->addStudyAndSeriesInstanceUID ( StudyInstanceUID.toStdString().c_str(), SeriesInstanceUID.c_str() ); // add the patient elements not provided for the series level query dataset->insert( patientName, true ); dataset->insert( patientID, true ); diff --git a/Libs/DICOM/Core/ctkDICOMQuery.h b/Libs/DICOM/Core/ctkDICOMQuery.h index 4c88f31ba0..8c374ae273 100644 --- a/Libs/DICOM/Core/ctkDICOMQuery.h +++ b/Libs/DICOM/Core/ctkDICOMQuery.h @@ -44,7 +44,7 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMQuery : public QObject Q_PROPERTY(QString host READ host WRITE setHost); Q_PROPERTY(int port READ port WRITE setPort); Q_PROPERTY(bool preferCGET READ preferCGET WRITE setPreferCGET); - Q_PROPERTY(QStringList studyInstanceUIDQueried READ studyInstanceUIDQueried); + Q_PROPERTY(QList> studyAndSeriesInstanceUIDQueried READ studyAndSeriesInstanceUIDQueried); Q_PROPERTY(QMap filters READ filters WRITE setFilters); public: @@ -75,8 +75,8 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMQuery : public QObject /// You must at least set the host and port before calling query() Q_INVOKABLE bool query(ctkDICOMDatabase& database); - /// Access the list of study instance UIDs from the last query - QStringList studyInstanceUIDQueried()const; + /// Access the list of study and series instance UIDs from the last query + QList> studyAndSeriesInstanceUIDQueried()const; /// /// Filters are keyword/value pairs as generated by diff --git a/Libs/DICOM/Widgets/Resources/UI/ctkDICOMQueryRetrieveWidget.ui b/Libs/DICOM/Widgets/Resources/UI/ctkDICOMQueryRetrieveWidget.ui index 843dbb466d..04cb0bfcde 100644 --- a/Libs/DICOM/Widgets/Resources/UI/ctkDICOMQueryRetrieveWidget.ui +++ b/Libs/DICOM/Widgets/Resources/UI/ctkDICOMQueryRetrieveWidget.ui @@ -6,29 +6,38 @@ 0 0 - 843 - 815 + 840 + 501 + + + 50 + false + + DICOM Query/Retrieve - - - QFrame::StyledPanel + + + Query - - QFrame::Raised + + false - - - - - Data Sources + + + + + QFrame::StyledPanel + + + QFrame::Raised - + @@ -39,82 +48,80 @@ + + + + Search Options + + + + + + + + - - - - Search Options + + + + + 75 + true + + + + Query - - - - - - - + + + + 50 + false + + + + Retrieve + + + true + + - - - Qt::Horizontal - - - - 90 - 20 - - - - - - - - Query - - + false - - Retrieve + + + 75 + true + - - - - - Close + Retrieve - - - - Qt::Horizontal - - - - 90 - 20 - - - - - + + + Close + + @@ -125,6 +132,12 @@
ctkDICOMTableManager.h
1 + + ctkCollapsibleButton + QWidget +
ctkCollapsibleButton.h
+ 1 +
ctkDICOMQueryWidget QWidget diff --git a/Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.cpp index 5fe5a600e4..93edde9be8 100644 --- a/Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.cpp @@ -19,6 +19,7 @@ =========================================================================*/ //Qt includes +#include #include #include #include @@ -61,6 +62,7 @@ class ctkDICOMQueryRetrieveWidgetPrivate: public Ui_ctkDICOMQueryRetrieveWidget QMap QueriesByServer; QMap QueriesByStudyUID; + std::list> StudyAndSeriesInstanceUIDPairList; QMap RetrievalsByStudyUID; ctkDICOMDatabase QueryResultDatabase; QSharedPointer RetrieveDatabase; @@ -103,10 +105,11 @@ void ctkDICOMQueryRetrieveWidgetPrivate::init() this->setupUi(q); QObject::connect(this->QueryWidget, SIGNAL(returnPressed()), q, SLOT(query())); + QObject::connect(this->QueryCollapsibleButton, SIGNAL(toggled(bool)), q, SLOT(onQuerySectionToggled(bool))); QObject::connect(this->QueryButton, SIGNAL(clicked()), q, SLOT(query())); + QObject::connect(this->RetrieveCollapsibleButton, SIGNAL(toggled(bool)), q, SLOT(onRetrieveSectionToggled(bool))); QObject::connect(this->RetrieveButton, SIGNAL(clicked()), q, SLOT(retrieve())); QObject::connect(this->CancelButton, SIGNAL(clicked()), q, SLOT(cancel())); - } //---------------------------------------------------------------------------- @@ -147,11 +150,33 @@ void ctkDICOMQueryRetrieveWidget::useProgressDialog(bool enable) d->UseProgressDialog=enable; } +//---------------------------------------------------------------------------- +void ctkDICOMQueryRetrieveWidget::onQuerySectionToggled(bool toggled) +{ + Q_D(ctkDICOMQueryRetrieveWidget); + if (toggled) + { + d->RetrieveCollapsibleButton->setCollapsed(true); + } +} + +//---------------------------------------------------------------------------- +void ctkDICOMQueryRetrieveWidget::onRetrieveSectionToggled(bool toggled) +{ + Q_D(ctkDICOMQueryRetrieveWidget); + if (toggled) + { + d->QueryCollapsibleButton->setCollapsed(true); + } +} + //---------------------------------------------------------------------------- void ctkDICOMQueryRetrieveWidget::query() { Q_D(ctkDICOMQueryRetrieveWidget); + d->QueryCollapsibleButton->setCollapsed(true); + d->RetrieveCollapsibleButton->setCollapsed(false); d->RetrieveButton->setEnabled(false); if (!d->QueryResultDatabase.isOpen()) @@ -238,10 +263,11 @@ void ctkDICOMQueryRetrieveWidget::query() } d->QueriesByServer[d->CurrentServer] = query; - - foreach( QString studyUID, query->studyInstanceUIDQueried() ) + + for (const auto & StudyAndSeriesInstanceUIDPair : query->studyAndSeriesInstanceUIDQueried() ) { - d->QueriesByStudyUID[studyUID] = query; + d->QueriesByStudyUID[StudyAndSeriesInstanceUIDPair.first] = query; + d->StudyAndSeriesInstanceUIDPairList.push_back(std::make_pair( StudyAndSeriesInstanceUIDPair.first, StudyAndSeriesInstanceUIDPair.second )); } } @@ -302,22 +328,28 @@ void ctkDICOMQueryRetrieveWidget::retrieve() // do the rerieval for each selected series // that is selected in the tree view - QStringList selectedSeriesUIDs = d->dicomTableManager->currentStudiesSelection(); - foreach( QString studyUID, selectedSeriesUIDs ) + QStringList selectedSeriesUIDs = d->dicomTableManager->currentSeriesSelection(); + + foreach( QString seriesUID, selectedSeriesUIDs ) { - std::cout<UseProgressDialog) { if (progress.wasCanceled()) { break; } - progressLabel->setText(QString(tr("Retrieving:\n%1")).arg(studyUID)); + progressLabel->setText(QString(tr("Retrieving:\n%1")).arg(seriesUID)); this->updateRetrieveProgress(0); } + // Get the study UID of the current series to be retrieved + auto currentStudyAndSeriesUIDPair = std::find_if( d->StudyAndSeriesInstanceUIDPairList.begin(), d->StudyAndSeriesInstanceUIDPairList.end(), + [&seriesUID]( const std::pair& element ) { return element.second == seriesUID; } ); + + QString studyUID = currentStudyAndSeriesUIDPair->first; + // Get information which server we want to get the study from and prepare request accordingly - ctkDICOMQuery *query = d->QueriesByStudyUID[studyUID]; + ctkDICOMQuery *query = d->QueriesByStudyUID[ studyUID ]; retrieve->setDatabase( d->RetrieveDatabase ); retrieve->setCallingAETitle( query->callingAETitle() ); retrieve->setCalledAETitle( query->calledAETitle() ); @@ -325,7 +357,7 @@ void ctkDICOMQueryRetrieveWidget::retrieve() retrieve->setHost( query->host() ); // TODO: check the model item to see if it is checked // for now, assume all studies queried and shown to the user will be retrieved - logger.debug("About to retrieve " + studyUID + " from " + d->QueriesByStudyUID[studyUID]->host()); + logger.debug("About to retrieve " + seriesUID + " from " + d->QueriesByStudyUID[ studyUID ]->host()); logger.info ( "Starting to retrieve" ); if(d->UseProgressDialog) @@ -341,11 +373,11 @@ void ctkDICOMQueryRetrieveWidget::retrieve() // perform the retrieve if ( query->preferCGET() ) { - retrieve->getStudy ( studyUID ); + retrieve->getSeries ( studyUID, seriesUID ); } else { - retrieve->moveStudy ( studyUID ); + retrieve->moveSeries ( studyUID, seriesUID ); } } catch (std::exception e) diff --git a/Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.h b/Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.h index b6d3496bfe..2c158cf90a 100644 --- a/Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.h +++ b/Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.h @@ -69,6 +69,8 @@ public Q_SLOTS: void canceled(); protected Q_SLOTS: + void onQuerySectionToggled(bool); + void onRetrieveSectionToggled(bool); void onQueryProgressChanged(int value); void updateRetrieveProgress(int value);