diff --git a/Libs/Core/ctkJobScheduler.cpp b/Libs/Core/ctkJobScheduler.cpp index 3495b7516a..0eec250d16 100644 --- a/Libs/Core/ctkJobScheduler.cpp +++ b/Libs/Core/ctkJobScheduler.cpp @@ -24,7 +24,8 @@ // Qt includes #include #include -#include +#include +#include #include #include #include @@ -68,20 +69,18 @@ void ctkJobSchedulerPrivate::init() void ctkJobSchedulerPrivate::queueJobsInThreadPool() { Q_Q(ctkJobScheduler); + // NOTE: No need to queue jobs with a signal/slot mechanism, since the mutex makes + // sure that concurrent threads append/clean/delete the jobs map. if (this->FreezeJobsScheduling) { return; } - // No need to queue jobs with a signal/slot mechanism, since the mutex makes - // sure that concurrent threads append/clean/delete the jobs map. - { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&this->QueueMutex); - + // The QWriteLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QWriteLockers within the scheduler's methods. + QWriteLocker locker(&this->QueueLock); foreach (QThread::Priority priority, (QList() << QThread::Priority::HighestPriority << QThread::Priority::HighPriority @@ -109,21 +108,24 @@ void ctkJobSchedulerPrivate::queueJobsInThreadPool() int numberOfRunningJobsWithSameType = this->getSameTypeJobsInThreadPoolQueueOrRunning(job); if (numberOfRunningJobsWithSameType >= job->maximumConcurrentJobsPerType()) { - continue; + // When the maximum number of concurrent jobs of the same type is reached, + // return early instead of adding more jobs to an already crowded queue. + // This allows the scheduler time to finish the currently running jobs, + // preventing a jobs traffic jam. + return; } logger.debug(QString("ctkDICOMScheduler: creating worker for job %1 in thread %2.\n") - .arg(job->jobUID()) - .arg(QString::number(reinterpret_cast(QThread::currentThreadId())), 16)); + .arg(job->jobUID()) + .arg(QString::number(reinterpret_cast(QThread::currentThreadId())), 16)); + + QSharedPointer worker = QSharedPointer(job->createWorker()); + worker->setScheduler(*q); + this->Workers.insert(job->jobUID(), worker); job->setStatus(ctkAbstractJob::JobStatus::Queued); emit q->jobQueued(job->toVariant()); - QSharedPointer worker = - QSharedPointer(job->createWorker()); - worker->setScheduler(*q); - - this->Workers.insert(job->jobUID(), worker); this->ThreadPool->start(worker.data(), job->priority()); } } @@ -184,16 +186,40 @@ bool ctkJobSchedulerPrivate::insertJob(QSharedPointer job) {"progress", progressConnection}, }; + emit q->jobInitialized(job->toVariant()); + + logger.debug(QString("ctkDICOMScheduler: creating worker for job %1 in thread %2.\n") + .arg(job->jobUID()) + .arg(QString::number(reinterpret_cast(QThread::currentThreadId())), 16)); + + QSharedPointer worker; { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&this->QueueMutex); + // The QWriteLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QWriteLockers within the scheduler's methods. + QWriteLocker locker(&this->QueueLock); this->JobsQueue.insert(job->jobUID(), job); this->JobsConnections.insert(job->jobUID(), connections); + + int numberOfRunningJobsWithSameType = this->getSameTypeJobsInThreadPoolQueueOrRunning(job); + if (numberOfRunningJobsWithSameType >= job->maximumConcurrentJobsPerType()) + { + return false; + } + + logger.debug(QString("ctkDICOMScheduler: creating worker for job %1 in thread %2.\n") + .arg(job->jobUID()) + .arg(QString::number(reinterpret_cast(QThread::currentThreadId())), 16)); + + QSharedPointer worker = QSharedPointer(job->createWorker()); + worker->setScheduler(*q); + this->Workers.insert(job->jobUID(), worker); + + job->setStatus(ctkAbstractJob::JobStatus::Queued); + emit q->jobQueued(job->toVariant()); + + this->ThreadPool->start(worker.data(), job->priority()); } - emit q->jobInitialized(job->toVariant()); - this->queueJobsInThreadPool(); return true; } @@ -205,11 +231,11 @@ bool ctkJobSchedulerPrivate::cleanJob(const QString &jobUID) .arg(QString::number(reinterpret_cast(QThread::currentThreadId()), 16))); { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&this->QueueMutex); + // The QWriteLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QWriteLockers within the scheduler's methods. + QWriteLocker locker(&this->QueueLock); QSharedPointer job = this->JobsQueue.value(jobUID); - if (!job || !this->JobsConnections.contains(jobUID)) + if (!job) { return false; } @@ -228,9 +254,9 @@ void ctkJobSchedulerPrivate::cleanJobs(const QStringList &jobUIDs) QList dataObjects; { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&this->QueueMutex); + // The QReadLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QReadLockers within the scheduler's methods. + QReadLocker locker(&this->QueueLock); foreach (QString jobUID, jobUIDs) { @@ -256,9 +282,9 @@ bool ctkJobSchedulerPrivate::removeJob(const QString& jobUID) .arg(QString::number(reinterpret_cast(QThread::currentThreadId()), 16))); { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&this->QueueMutex); + // The QWriteLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QWriteLockers within the scheduler's methods. + QWriteLocker locker(&this->QueueLock); QSharedPointer job = this->JobsQueue.value(jobUID); if (!job || !this->JobsConnections.contains(jobUID)) { @@ -284,11 +310,10 @@ bool ctkJobSchedulerPrivate::removeJob(const QString& jobUID) //------------------------------------------------------------------------------ void ctkJobSchedulerPrivate::removeJobs(const QStringList &jobUIDs) { - QList dataObjects; { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&this->QueueMutex); + // The QWriteLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QWriteLockers within the scheduler's methods. + QWriteLocker locker(&this->QueueLock); foreach (QString jobUID, jobUIDs) { @@ -298,8 +323,6 @@ void ctkJobSchedulerPrivate::removeJobs(const QStringList &jobUIDs) continue; } - dataObjects.append(job->toVariant()); - QMap connections = this->JobsConnections.value(jobUID); QObject::disconnect(connections.value("started")); QObject::disconnect(connections.value("userStopped")); @@ -377,7 +400,6 @@ ctkJobScheduler::ctkJobScheduler(ctkJobSchedulerPrivate* pimpl, QObject* parent) // -------------------------------------------------------------------------- ctkJobScheduler::~ctkJobScheduler() { - this->setFreezeJobsScheduling(true); this->stopAllJobs(true); // stopAllJobs is not main thread blocking. Therefore we need actually // to wait the jobs to end (either finished or stopped) before closing the application. @@ -401,9 +423,9 @@ int ctkJobScheduler::numberOfJobs() Q_D(ctkJobScheduler); int numberOfJobs = 0; { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&d->QueueMutex); + // The QReadLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QReadLockers within the scheduler's methods. + QReadLocker locker(&d->QueueLock); numberOfJobs = d->JobsQueue.count(); } return numberOfJobs; @@ -415,9 +437,9 @@ int ctkJobScheduler::numberOfPersistentJobs() Q_D(ctkJobScheduler); int numberOfPersistentJobs = 0; { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&d->QueueMutex); + // The QReadLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QReadLockers within the scheduler's methods. + QReadLocker locker(&d->QueueLock); foreach (QSharedPointer job, d->JobsQueue) { if (job->isPersistent()) @@ -437,9 +459,9 @@ int ctkJobScheduler::numberOfRunningJobs() int numberOfRunningJobs = 0; { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&d->QueueMutex); + // The QReadLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QReadLockers within the scheduler's methods. + QReadLocker locker(&d->QueueLock); foreach (QSharedPointer job, d->JobsQueue) { if (job->status() <= ctkAbstractJob::JobStatus::Running) @@ -503,9 +525,9 @@ QSharedPointer ctkJobScheduler::getJobSharedByUID(const QString& QSharedPointer job = nullptr; { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&d->QueueMutex); + // The QReadLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QReadLockers within the scheduler's methods. + QReadLocker locker(&d->QueueLock); QMap>::iterator it = d->JobsQueue.find(jobUID); if (it == d->JobsQueue.end()) { @@ -566,11 +588,12 @@ QStringList ctkJobScheduler::stopAllJobs(bool stopPersistentJobs, bool removeJob { Q_D(ctkJobScheduler); + d->FreezeJobsScheduling = true; QStringList stoppedJobsUIDs; { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&d->QueueMutex); + // The QReadLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QReadLockers within the scheduler's methods. + QReadLocker locker(&d->QueueLock); // Stops jobs without a worker (in waiting, still in main thread). foreach (QSharedPointer job, d->JobsQueue) @@ -586,7 +609,7 @@ QStringList ctkJobScheduler::stopAllJobs(bool stopPersistentJobs, bool removeJob } QString jobUID = job->jobUID(); - if (!d->JobsConnections.contains(jobUID)) + if (jobUID.isEmpty() || !d->JobsConnections.contains(jobUID)) { continue; } @@ -600,6 +623,7 @@ QStringList ctkJobScheduler::stopAllJobs(bool stopPersistentJobs, bool removeJob QMap connections = d->JobsConnections.value(jobUID); QObject::disconnect(connections.value("userStopped")); job->setStatus(ctkAbstractJob::JobStatus::UserStopped); + d->BatchedJobsUserStopped.append(job->toVariant()); stoppedJobsUIDs.append(jobUID); } } @@ -626,6 +650,13 @@ QStringList ctkJobScheduler::stopAllJobs(bool stopPersistentJobs, bool removeJob worker->requestCancel(); } + d->FreezeJobsScheduling = false; + + if (!d->ThrottleTimer->isActive()) + { + d->ThrottleTimer->start(d->ThrottleTimeInterval); + } + return stoppedJobsUIDs; } @@ -641,9 +672,9 @@ void ctkJobScheduler::stopJobsByJobUIDs(const QStringList &jobUIDs, bool removeJ QStringList initializedStoppedJobsUIDs; { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&d->QueueMutex); + // The QWriteLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QWriteLockers within the scheduler's methods. + QWriteLocker locker(&d->QueueLock); // Stops jobs without a worker (in waiting, still in main thread) foreach (QSharedPointer job, d->JobsQueue) @@ -674,6 +705,7 @@ void ctkJobScheduler::stopJobsByJobUIDs(const QStringList &jobUIDs, bool removeJ QMap connections = d->JobsConnections.value(jobUID); QObject::disconnect(connections.value("userStopped")); job->setStatus(ctkAbstractJob::JobStatus::UserStopped); + d->BatchedJobsUserStopped.append(job->toVariant()); initializedStoppedJobsUIDs.append(job->jobUID()); } } @@ -702,6 +734,11 @@ void ctkJobScheduler::stopJobsByJobUIDs(const QStringList &jobUIDs, bool removeJ worker->requestCancel(); } } + + if (!d->ThrottleTimer->isActive()) + { + d->ThrottleTimer->start(d->ThrottleTimeInterval); + } } //---------------------------------------------------------------------------- diff --git a/Libs/Core/ctkJobScheduler.h b/Libs/Core/ctkJobScheduler.h index cbe20c629d..1e61c6deb6 100644 --- a/Libs/Core/ctkJobScheduler.h +++ b/Libs/Core/ctkJobScheduler.h @@ -58,7 +58,7 @@ class CTK_CORE_EXPORT ctkJobScheduler : public QObject int numberOfJobs(); int numberOfPersistentJobs(); int numberOfRunningJobs(); - Q_INVOKABLE void addJob(ctkAbstractJob* job); + Q_INVOKABLE virtual void addJob(ctkAbstractJob* job); Q_INVOKABLE virtual void resetJob(const QString& jobUID); Q_INVOKABLE virtual void deleteJob(const QString& jobUID); Q_INVOKABLE virtual void deleteJobs(const QStringList& jobUIDs); diff --git a/Libs/Core/ctkJobScheduler_p.h b/Libs/Core/ctkJobScheduler_p.h index d6313f2f0c..e3810c44a6 100644 --- a/Libs/Core/ctkJobScheduler_p.h +++ b/Libs/Core/ctkJobScheduler_p.h @@ -22,7 +22,7 @@ #define __ctkJobSchedulerPrivate_h // Qt includes -#include +#include #include #include class QThreadPool; @@ -61,7 +61,7 @@ class CTK_CORE_EXPORT ctkJobSchedulerPrivate : public QObject virtual void queueJobsInThreadPool(); virtual void clearBactchedJobsLists(); - QMutex QueueMutex; + QReadWriteLock QueueLock; int RetryDelay{100}; int MaximumNumberOfRetry{3}; @@ -71,6 +71,7 @@ class CTK_CORE_EXPORT ctkJobSchedulerPrivate : public QObject QMap> JobsQueue; QMap> JobsConnections; QMap> Workers; + QMap RunningJobsByJobClass; QList BatchedJobsStarted; QList BatchedJobsUserStopped; QList BatchedJobsFinished; diff --git a/Libs/DICOM/Core/ctkDICOMDatabase.cpp b/Libs/DICOM/Core/ctkDICOMDatabase.cpp index 186428af95..66ba36e3a5 100644 --- a/Libs/DICOM/Core/ctkDICOMDatabase.cpp +++ b/Libs/DICOM/Core/ctkDICOMDatabase.cpp @@ -305,10 +305,10 @@ QStringList ctkDICOMDatabasePrivate::filenames(QString table) } //------------------------------------------------------------------------------ -bool ctkDICOMDatabasePrivate::insertPatient(const ctkDICOMItem& dataset, - const QString& patientID, - const QString& patientsName, - int& dbPatientID) +int ctkDICOMDatabasePrivate::insertPatient(const ctkDICOMItem& dataset, + const QString& patientID, + const QString& patientsName, + int& dbPatientID) { // Check if patient is already present in the db QString tempPatientID(patientID), tempPatientsName(patientsName), patientsBirthDate; @@ -362,7 +362,7 @@ bool ctkDICOMDatabasePrivate::insertPatient(const ctkDICOMItem& dataset, insertPatientStatement.bindValue(7, QDateTime::currentDateTime()); if (!loggedExec(insertPatientStatement)) { - return false; + return -1; } dbPatientID = insertPatientStatement.lastInsertId().toInt(); @@ -374,11 +374,10 @@ bool ctkDICOMDatabasePrivate::insertPatient(const ctkDICOMItem& dataset, } //------------------------------------------------------------------------------ -bool ctkDICOMDatabasePrivate::insertConnectionName(const int& dbPatientID, - const QString& connectionName) +int ctkDICOMDatabasePrivate::insertConnectionName(const int& dbPatientID, + const QString& connectionName) { Q_Q(ctkDICOMDatabase); - // check if connection name is already stored QMap connectionsInformation = q->connectionsInformationForPatient(QString::number(dbPatientID)); QStringList allowList = connectionsInformation["allow"]; @@ -404,7 +403,7 @@ bool ctkDICOMDatabasePrivate::insertConnectionName(const int& dbPatientID, if (!loggedExec(updateConnectionsStatement)) { - return false; + return -1; } logger.debug("New connection name inserted: patient database item ID = " + QString().setNum(dbPatientID)); } @@ -425,12 +424,11 @@ bool ctkDICOMDatabasePrivate::insertConnectionName(const int& dbPatientID, } //------------------------------------------------------------------------------ -bool ctkDICOMDatabasePrivate::updateConnections(const QString& dbPatientID, - const QStringList& allowList, - const QStringList& denyList) +int ctkDICOMDatabasePrivate::updateConnections(const QString& dbPatientID, + const QStringList& allowList, + const QStringList& denyList) { QString connectionsData = this->convertConnectionInfoToJson(allowList, denyList); - QSqlQuery updateConnectionsStatement(this->Database); updateConnectionsStatement.prepare("UPDATE Patients SET Connections = :connectionsData WHERE UID = :uid"); updateConnectionsStatement.bindValue(":connectionsData", connectionsData); @@ -438,14 +436,14 @@ bool ctkDICOMDatabasePrivate::updateConnections(const QString& dbPatientID, if (!loggedExec(updateConnectionsStatement)) { - return false; + return -1; } return true; } //------------------------------------------------------------------------------ -bool ctkDICOMDatabasePrivate::insertStudy(const ctkDICOMItem& dataset, const int& dbPatientID) +int ctkDICOMDatabasePrivate::insertStudy(const ctkDICOMItem& dataset, const int& dbPatientID) { QString studyInstanceUID(dataset.GetElementAsString(DCM_StudyInstanceUID) ); QSqlQuery checkStudyExistsQuery(this->Database); @@ -453,7 +451,7 @@ bool ctkDICOMDatabasePrivate::insertStudy(const ctkDICOMItem& dataset, const int checkStudyExistsQuery.bindValue( 0, studyInstanceUID ); if (!loggedExec(checkStudyExistsQuery)) { - return false; + return -1; } if (!checkStudyExistsQuery.next()) { @@ -489,7 +487,7 @@ bool ctkDICOMDatabasePrivate::insertStudy(const ctkDICOMItem& dataset, const int if (!insertStudyStatement.exec()) { logger.error("Error executing statement: " + insertStudyStatement.lastQuery() + " Error: " + insertStudyStatement.lastError().text() ); - return false; + return -1; } else { @@ -507,7 +505,7 @@ bool ctkDICOMDatabasePrivate::insertStudy(const ctkDICOMItem& dataset, const int } //------------------------------------------------------------------------------ -bool ctkDICOMDatabasePrivate::insertSeries(const ctkDICOMItem& dataset, const QString& studyInstanceUID) +int ctkDICOMDatabasePrivate::insertSeries(const ctkDICOMItem& dataset, const QString& studyInstanceUID) { QString seriesInstanceUID(dataset.GetElementAsString(DCM_SeriesInstanceUID) ); QSqlQuery checkSeriesExistsQuery(this->Database); @@ -516,7 +514,7 @@ bool ctkDICOMDatabasePrivate::insertSeries(const ctkDICOMItem& dataset, const QS logger.debug("Statement: " + checkSeriesExistsQuery.lastQuery() ); if (!loggedExec(checkSeriesExistsQuery)) { - return false; + return -1; } if (!checkSeriesExistsQuery.next()) { @@ -560,7 +558,7 @@ bool ctkDICOMDatabasePrivate::insertSeries(const ctkDICOMItem& dataset, const QS logger.error("Error executing statement: " + insertSeriesStatement.lastQuery() + " Error: " + insertSeriesStatement.lastError().text()); - return false; + return -1; } else { @@ -780,7 +778,7 @@ bool ctkDICOMDatabasePrivate::indexingStatusForFile(const QString& filePath, con } //------------------------------------------------------------------------------ -bool ctkDICOMDatabasePrivate::insertPatientStudySeries(const ctkDICOMItem& dataset, +int ctkDICOMDatabasePrivate::insertPatientStudySeries(const ctkDICOMItem& dataset, const QString& patientID, const QString& patientsName, const QString& connectionName) { Q_Q(ctkDICOMDatabase); @@ -803,11 +801,16 @@ bool ctkDICOMDatabasePrivate::insertPatientStudySeries(const ctkDICOMItem& datas else { logger.debug("Insert new patient if not already in database: " + patientID + " " + patientsName); - if (this->insertPatient(dataset, patientID, patientsName, dbPatientID)) + int patientMetadataInsertSuccess = this->insertPatient(dataset, patientID, patientsName, dbPatientID); + if (patientMetadataInsertSuccess == 1) { databaseWasChanged = true; emit q->patientAdded(dbPatientID, patientID, patientsName, patientsBirthDate); } + else if (patientMetadataInsertSuccess == -1) + { + return -1; + } } QMap::iterator dbConnectionsit = @@ -820,11 +823,16 @@ bool ctkDICOMDatabasePrivate::insertPatientStudySeries(const ctkDICOMItem& datas if (connections.count() == 0 || !connections.contains(connectionName)) { logger.debug("Insert new connection name if not already in database: " + connectionName); - if (this->insertConnectionName(dbPatientID, connectionName)) + int connectionMetadataInsertSuccess = this->insertConnectionName(dbPatientID, connectionName); + if (connectionMetadataInsertSuccess == 1) { databaseWasChanged = true; emit q->connectionNameAdded(dbPatientID, patientID, patientsName, patientsBirthDate, connectionName); } + else if (connectionMetadataInsertSuccess == -1) + { + return -1; + } } logger.debug("Going to insert this instance with dbPatientID: " + QString::number(dbPatientID)); @@ -833,24 +841,34 @@ bool ctkDICOMDatabasePrivate::insertPatientStudySeries(const ctkDICOMItem& datas QString studyInstanceUID(dataset.GetElementAsString(DCM_StudyInstanceUID)); if (!this->InsertedStudyUIDsCache.contains(studyInstanceUID)) { - if (this->insertStudy(dataset, dbPatientID)) + int studyMetadataInsertSuccess = this->insertStudy(dataset, dbPatientID); + if (studyMetadataInsertSuccess == 1) { logger.debug("Study Added"); databaseWasChanged = true; // let users of this class track when things happen emit q->studyAdded(studyInstanceUID); } + else if (studyMetadataInsertSuccess == -1) + { + return -1; + } } QString seriesInstanceUID(dataset.GetElementAsString(DCM_SeriesInstanceUID)); if (!seriesInstanceUID.isEmpty() && !this->InsertedSeriesUIDsCache.contains(seriesInstanceUID)) { - if (this->insertSeries(dataset, studyInstanceUID)) + int seriesMetadataInsertSuccess = this->insertSeries(dataset, studyInstanceUID); + if (seriesMetadataInsertSuccess == 1) { logger.debug("Series Added"); databaseWasChanged = true; emit q->seriesAdded(seriesInstanceUID); } + else if (seriesMetadataInsertSuccess == -1) + { + return -1; + } } return databaseWasChanged; @@ -2901,11 +2919,12 @@ void ctkDICOMDatabase::insert(const QList& ind } //------------------------------------------------------------------------------ -void ctkDICOMDatabase::insert(const QList& jobResponseSets) +bool ctkDICOMDatabase::insert(const QList& jobResponseSets) { Q_D(ctkDICOMDatabase); bool databaseWasChanged = false; + bool insertSuccess = true; d->TagCacheDatabase.transaction(); d->Database.transaction(); @@ -3090,10 +3109,15 @@ void ctkDICOMDatabase::insert(const QList& jobResponseS } } - if (d->insertPatientStudySeries(*dataset, patientID, patientName, connectionName)) + int metadataInsertSuccess = d->insertPatientStudySeries(*dataset, patientID, patientName, connectionName); + if (metadataInsertSuccess == 1) { databaseWasChanged = true; } + else if (metadataInsertSuccess == -1) + { + insertSuccess = false; + } if (!sopInstanceUID.isEmpty() && !seriesInstanceUID.isEmpty() && @@ -3109,7 +3133,8 @@ void ctkDICOMDatabase::insert(const QList& jobResponseS checkImageExistsQuery.addBindValue(sopInstanceUID); if (!d->loggedExec(checkImageExistsQuery)) { - return; + insertSuccess = false; + continue; } alreadyInserted = checkImageExistsQuery.next(); } @@ -3138,9 +3163,11 @@ void ctkDICOMDatabase::insert(const QList& jobResponseS if (!insertImageStatement.exec()) { - logger.error( "Error executing statement: " + logger.error("Error executing statement: " + insertImageStatement.lastQuery() - + " Error: " + insertImageStatement.lastError().text() ); + + " Error: " + insertImageStatement.lastError().text()); + insertSuccess = false; + continue; } else { @@ -3167,6 +3194,8 @@ void ctkDICOMDatabase::insert(const QList& jobResponseS { emit this->databaseChanged(); } + + return insertSuccess; } //------------------------------------------------------------------------------ diff --git a/Libs/DICOM/Core/ctkDICOMDatabase.h b/Libs/DICOM/Core/ctkDICOMDatabase.h index 82365fc314..b1e5a1446b 100644 --- a/Libs/DICOM/Core/ctkDICOMDatabase.h +++ b/Libs/DICOM/Core/ctkDICOMDatabase.h @@ -281,7 +281,7 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMDatabase : public QObject bool createHierarchy = true, const QString& destinationDirectoryName = QString()); Q_INVOKABLE void insert(const QList& indexingResults); - Q_INVOKABLE void insert(const QList& jobResponseSets); + Q_INVOKABLE bool insert(const QList& jobResponseSets); /// When a DICOM file is stored in the database (insert is called with storeFile=true) then /// path is constructed from study, series, and SOP instance UID. diff --git a/Libs/DICOM/Core/ctkDICOMDatabase_p.h b/Libs/DICOM/Core/ctkDICOMDatabase_p.h index 87ad90bbb5..50f4dff435 100644 --- a/Libs/DICOM/Core/ctkDICOMDatabase_p.h +++ b/Libs/DICOM/Core/ctkDICOMDatabase_p.h @@ -174,23 +174,25 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMDatabasePrivate bool openTagCacheDatabase(); void precacheTags(const ctkDICOMItem& dataset, const QString sopInstanceUID); - // Return true if a new item is inserted - bool insertPatientStudySeries(const ctkDICOMItem& dataset, + // Return 1 if a new item is inserted + // Return 0 if a no item is inserted + // Return -1 if an error occurred + int insertPatientStudySeries(const ctkDICOMItem& dataset, const QString& patientID, const QString& patientsName, const QString& connectionName = ""); - bool insertPatient(const ctkDICOMItem& dataset, + int insertPatient(const ctkDICOMItem& dataset, const QString& patientID, const QString& patientsName, int& databasePatientID); - bool insertConnectionName(const int& dbPatientID, + int insertConnectionName(const int& dbPatientID, const QString& connectionName); - bool updateConnections(const QString& dbPatientID, + int updateConnections(const QString& dbPatientID, const QStringList& allowList, const QStringList& denyList); - bool insertStudy(const ctkDICOMItem& dataset, + int insertStudy(const ctkDICOMItem& dataset, const int& dbPatientID); - bool insertSeries(const ctkDICOMItem& dataset, + int insertSeries(const ctkDICOMItem& dataset, const QString& studyInstanceUID); /// Facilitate using custom schema with the database without subclassing diff --git a/Libs/DICOM/Core/ctkDICOMInserter.cpp b/Libs/DICOM/Core/ctkDICOMInserter.cpp index fcb337e226..ca640cdde4 100644 --- a/Libs/DICOM/Core/ctkDICOMInserter.cpp +++ b/Libs/DICOM/Core/ctkDICOMInserter.cpp @@ -107,15 +107,14 @@ bool ctkDICOMInserter::addJobResponseSets(const QList& // to determine if any other process is currently writing (for example, a UI element writing the patient's name into the database). // Therefore, we propose the inclusion of a static variable in ctkDICOMDatabase that indicates ongoing write operations // for each DatabaseFilename, except in cases where it is an in-memory database. - database.insert(jobResponseSets); + bool success = database.insert(jobResponseSets); database.updateDisplayedFields(); - database.closeDatabase(); emit updatingDatabase(false); emit done(); - return true; + return success; } //---------------------------------------------------------------------------- diff --git a/Libs/DICOM/Core/ctkDICOMInserterJob.cpp b/Libs/DICOM/Core/ctkDICOMInserterJob.cpp index 61dc97e3c4..bc0f626b42 100644 --- a/Libs/DICOM/Core/ctkDICOMInserterJob.cpp +++ b/Libs/DICOM/Core/ctkDICOMInserterJob.cpp @@ -45,24 +45,27 @@ QString ctkDICOMInserterJob::loggerReport(const QString& status) { QString fullLogMsg; QString logMsg; - if (status == "started") - { - fullLogMsg = QString("ctkDICOMInserterJob: insert job %1. " - "Number of jobResponseSet to process: %2\n") - .arg(status) - .arg(this->JobResponseSets.count()); - logMsg = QString("Insert job %1. " - "Number of jobResponseSet to process: %2\n") - .arg(status) - .arg(this->JobResponseSets.count()); - } - else + + QString uids; + foreach (QSharedPointer JobResponseSet, this->JobResponseSets) { - fullLogMsg = QString("ctkDICOMInserterJob: insert job %1.\n") - .arg(status); - logMsg = QString("insert job %1. ") - .arg(status); + uids += "job type : " + JobResponseSet->jobTypeString() + ": \n" ; + uids += JobResponseSet->datasets().keys().join(", ") + ": \n"; } + + fullLogMsg = QString("ctkDICOMInserterJob: insert job %1. " + "Number of jobResponseSet processing: %2.\n " + "uids: \n %3\n") + .arg(status) + .arg(this->JobResponseSets.count()) + .arg(uids); + logMsg = QString("Insert job %1. " + "Number of jobResponseSet processing: %2.\n " + "uids: \n %3\n") + .arg(status) + .arg(this->JobResponseSets.count()) + .arg(uids); + QString currentDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz"); QString logHeader = currentDateTime + " INFO: "; this->Log += logHeader; diff --git a/Libs/DICOM/Core/ctkDICOMInserterWorker.cpp b/Libs/DICOM/Core/ctkDICOMInserterWorker.cpp index 160fa91dcc..84480bbb7f 100644 --- a/Libs/DICOM/Core/ctkDICOMInserterWorker.cpp +++ b/Libs/DICOM/Core/ctkDICOMInserterWorker.cpp @@ -119,7 +119,11 @@ void ctkDICOMInserterWorker::run() .arg(QString::number(reinterpret_cast(QThread::currentThreadId())), 16)); QList jobResponseSets = inserterJob->jobResponseSets(); - d->Inserter->addJobResponseSets(jobResponseSets); + if (!d->Inserter->addJobResponseSets(jobResponseSets)) + { + inserterJob->setStatus(ctkAbstractJob::JobStatus::Failed); + return; + } if (d->Inserter->wasCanceled()) { diff --git a/Libs/DICOM/Core/ctkDICOMJobResponseSet.cpp b/Libs/DICOM/Core/ctkDICOMJobResponseSet.cpp index 390c2b714b..087ec1cee7 100644 --- a/Libs/DICOM/Core/ctkDICOMJobResponseSet.cpp +++ b/Libs/DICOM/Core/ctkDICOMJobResponseSet.cpp @@ -135,6 +135,42 @@ void ctkDICOMJobResponseSet::setFilePath(const QString& filePath) d->Datasets.insert(QString(SOPInstanceUID.c_str()), dataset); } +//---------------------------------------------------------------------------- +QString ctkDICOMJobResponseSet::jobTypeString() const +{ + Q_D(const ctkDICOMJobResponseSet); + + switch (d->JobType) + { + case ctkDICOMJobResponseSet::JobType::None: + return "None"; + case ctkDICOMJobResponseSet::JobType::QueryPatients: + return "QueryPatients"; + case ctkDICOMJobResponseSet::JobType::QueryStudies: + return "QueryStudies"; + case ctkDICOMJobResponseSet::JobType::QuerySeries: + return "QuerySeries"; + case ctkDICOMJobResponseSet::JobType::QueryInstances: + return "QueryInstances"; + case ctkDICOMJobResponseSet::JobType::RetrieveStudy: + return "RetrieveStudy"; + case ctkDICOMJobResponseSet::JobType::RetrieveSeries: + return "RetrieveSeries"; + case ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance: + return "RetrieveSOPInstance"; + case ctkDICOMJobResponseSet::JobType::StoreSOPInstance: + return "StoreSOPInstance"; + case ctkDICOMJobResponseSet::JobType::Inserter: + return "Inserter"; + case ctkDICOMJobResponseSet::JobType::Echo: + return "Echo"; + case ctkDICOMJobResponseSet::JobType::ThumbnailGenerator: + return "ThumbnailGenerator"; + default: + return "Unknown"; + } +} + //------------------------------------------------------------------------------ void ctkDICOMJobResponseSet::setDataset(DcmItem* dcmItem, bool takeOwnership) { diff --git a/Libs/DICOM/Core/ctkDICOMJobResponseSet.h b/Libs/DICOM/Core/ctkDICOMJobResponseSet.h index 1aba0605be..4f7952c75e 100644 --- a/Libs/DICOM/Core/ctkDICOMJobResponseSet.h +++ b/Libs/DICOM/Core/ctkDICOMJobResponseSet.h @@ -94,6 +94,7 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMJobResponseSet : public QObject }; void setJobType(JobType jobType); JobType jobType() const; + QString jobTypeString() const; ///@} ///@{ diff --git a/Libs/DICOM/Core/ctkDICOMScheduler.cpp b/Libs/DICOM/Core/ctkDICOMScheduler.cpp index 6377fd471b..f1c80fdf39 100644 --- a/Libs/DICOM/Core/ctkDICOMScheduler.cpp +++ b/Libs/DICOM/Core/ctkDICOMScheduler.cpp @@ -178,45 +178,6 @@ ctkDICOMServer* ctkDICOMSchedulerPrivate::getServerFromProxyServersByConnectionN return nullptr; } -//------------------------------------------------------------------------------ -bool ctkDICOMSchedulerPrivate::isJobDuplicate(ctkDICOMJob *referenceJob) -{ - bool duplicate = false; - { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&this->QueueMutex); - foreach (QSharedPointer job, this->JobsQueue) - { - if (!job) - { - continue; - } - - ctkDICOMJob* dicomJob = qobject_cast(job.data()); - if (!dicomJob) - { - logger.debug("ctkDICOMScheduler::getJobsByDICOMUIDs: unexpected type of job."); - continue; - } - - if (dicomJob->className() == referenceJob->className() && - dicomJob->patientID() == referenceJob->patientID() && - dicomJob->studyInstanceUID() == referenceJob->studyInstanceUID() && - dicomJob->seriesInstanceUID() == referenceJob->seriesInstanceUID() && - dicomJob->sopInstanceUID() == referenceJob->sopInstanceUID() && - dicomJob->dicomLevel() == referenceJob->dicomLevel() && - dicomJob->status() < ctkAbstractJob::JobStatus::UserStopped) - { - duplicate = true; - break; - } - } - } - - return duplicate; -} - //------------------------------------------------------------------------------ // ctkDICOMScheduler methods @@ -531,10 +492,7 @@ void ctkDICOMScheduler::generateThumbnail(const QString &originalFilePath, job->setMaximumNumberOfRetry(0); job->setPriority(priority); - if (!d->isJobDuplicate(job.data())) - { - d->insertJob(job); - } + d->insertJob(job); } //---------------------------------------------------------------------------- @@ -846,9 +804,9 @@ void ctkDICOMScheduler::waitForFinishByDICOMUIDs(const QStringList& patientIDs, } { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&d->QueueMutex); + // The QReadLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QReadLockers within the scheduler's methods. + QReadLocker locker(&d->QueueLock); bool wait = true; while (wait) { @@ -921,9 +879,9 @@ QList> ctkDICOMScheduler::getJobsByDICOMUIDs(cons } { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&d->QueueMutex); + // The QReadLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QReadLockers within the scheduler's methods. + QReadLocker locker(&d->QueueLock); foreach (QSharedPointer job, d->JobsQueue) { if (!job) @@ -982,9 +940,9 @@ void ctkDICOMScheduler::stopJobsByDICOMUIDs(const QStringList& patientIDs, QStringList jobsUIDs; { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&d->QueueMutex); + // The QReadLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QReadLockers within the scheduler's methods. + QReadLocker locker(&d->QueueLock); // Stops jobs without a worker (in waiting, still in main thread) foreach (QSharedPointer job, d->JobsQueue) { @@ -1025,9 +983,9 @@ void ctkDICOMScheduler::raiseJobsPriorityForSeries(const QStringList& selectedSe } { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&d->QueueMutex); + // The QWriteLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QWriteLockers within the scheduler's methods. + QWriteLocker locker(&d->QueueLock); foreach (QSharedPointer job, d->JobsQueue) { if (job->isPersistent()) @@ -1073,9 +1031,9 @@ ctkDICOMStorageListenerJob* ctkDICOMScheduler::listenerJob() ctkDICOMStorageListenerJob* listenerJobRaw = nullptr; { - // The QMutexLocker is enclosed within brackets to restrict its scope and - // prevent conflicts with other QMutexLockers within the scheduler's methods. - QMutexLocker locker(&d->QueueMutex); + // The QWriteLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QWriteLockers within the scheduler's methods. + QWriteLocker locker(&d->QueueLock); foreach (QSharedPointer job, d->JobsQueue) { QSharedPointer listenerJob = diff --git a/Libs/DICOM/Core/ctkDICOMThumbnailGenerator.cpp b/Libs/DICOM/Core/ctkDICOMThumbnailGenerator.cpp index e076c9255e..51d5bc1439 100644 --- a/Libs/DICOM/Core/ctkDICOMThumbnailGenerator.cpp +++ b/Libs/DICOM/Core/ctkDICOMThumbnailGenerator.cpp @@ -135,7 +135,7 @@ bool ctkDICOMThumbnailGenerator::generateThumbnail(DicomImage *dcmImage, QImage& EI_Status result = dcmImage->getStatus(); if (result != EIS_Normal) { - QString warn = QString("Rendering of DICOM image failed for thumbnail failed: ") + DicomImage::getString(result); + QString warn = QString("Rendering of DICOM image failed for thumbnail failed: %1").arg(DicomImage::getString(result)); DCMTK_LOG4CPLUS_WARN_STR(rootLogThumbnailGenerator, warn.toStdString().c_str()); return false; } @@ -184,7 +184,7 @@ bool ctkDICOMThumbnailGenerator::generateThumbnail(DicomImage *dcmImage, QImage& { if (!image.loadFromData( buffer )) { - qCritical() << Q_FUNC_INFO << "QImage couldn't created"; + DCMTK_LOG4CPLUS_ERROR_STR(rootLogThumbnailGenerator, "QImage couldn't created"); return false; } } @@ -203,6 +203,7 @@ bool ctkDICOMThumbnailGenerator::generateThumbnail(DicomImage *dcmImage, const Q return image.save(thumbnailPath, "PNG"); } + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogThumbnailGenerator, "Thumbnail generation failed, using a document icon instead."); this->generateDocumentThumbnail(thumbnailPath, backgroundColor); return false; } diff --git a/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp index 1e3b904eb2..5e46a96d6a 100644 --- a/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp @@ -123,6 +123,7 @@ class QCenteredItemModel : public QStandardItemModel QStringList clearCompletedJobs(); QStringList clearFailedJobs(); QStringList clearUserStoppedJobs(); + QStringList clearQueuedJobs(); void removeRowsByJobUIDs(QStringList jobUIDs); static Columns getColumnIndexFromString(QString columnString); static QString getColumnStringFromIndex(Columns columnIndex); @@ -466,13 +467,19 @@ QStringList QCenteredItemModel::clearCompletedJobs() //---------------------------------------------------------------------------- QStringList QCenteredItemModel::clearFailedJobs() { - return this->clearJobsByType(ctkDICOMJobListWidget::tr("failed"));; + return this->clearJobsByType(ctkDICOMJobListWidget::tr("failed")); } //---------------------------------------------------------------------------- QStringList QCenteredItemModel::clearUserStoppedJobs() { - return this->clearJobsByType(ctkDICOMJobListWidget::tr("user-stopped"));; + return this->clearJobsByType(ctkDICOMJobListWidget::tr("user-stopped")); +} + +//---------------------------------------------------------------------------- +QStringList QCenteredItemModel::clearQueuedJobs() +{ + return this->clearJobsByType(ctkDICOMJobListWidget::tr("queued")); } //---------------------------------------------------------------------------- diff --git a/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp index ec7cbd7089..876d3d4af3 100644 --- a/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp @@ -370,6 +370,11 @@ void ctkDICOMPatientItemWidgetPrivate::createStudies() studiesMap[key] = studyItemWidget; } + QSettings settings; + bool queryRetrieveEnabled = settings.value("DICOM/QueryRetrieveEnabled", "").toBool(); + bool queryEnabled = this->QueryOn && queryRetrieveEnabled; + bool retrieveEnabled = this->RetrieveOn && queryRetrieveEnabled; + int cont = 0; foreach (ctkDICOMStudyItemWidget* studyItemWidget, studiesMap) { @@ -377,11 +382,11 @@ void ctkDICOMPatientItemWidgetPrivate::createStudies() if (cont < this->NumberOfOpenedStudiesPerPatient) { studyItemWidget->setCollapsed(false); - studyItemWidget->generateSeries(this->QueryOn, this->RetrieveOn); + studyItemWidget->generateSeries(queryEnabled, retrieveEnabled); } else { - studyItemWidget->generateSeries(this->QueryOn, false); + studyItemWidget->generateSeries(queryEnabled, false); } cont++; @@ -869,6 +874,8 @@ void ctkDICOMPatientItemWidget::generateStudies(bool query, bool retrieve) //------------------------------------------------------------------------------ void ctkDICOMPatientItemWidget::generateSeriesAtToggle(bool toggled, const QString& studyItem) { + Q_D(ctkDICOMPatientItemWidget); + if (!toggled || studyItem.isEmpty()) { return; @@ -880,7 +887,12 @@ void ctkDICOMPatientItemWidget::generateSeriesAtToggle(bool toggled, const QStri return; } - studyItemWidget->generateSeries(); + QSettings settings; + bool queryRetrieveEnabled = settings.value("DICOM/QueryRetrieveEnabled", "").toBool(); + bool queryEnabled = d->QueryOn && queryRetrieveEnabled; + bool retrieveEnabled = d->RetrieveOn && queryRetrieveEnabled; + + studyItemWidget->generateSeries(queryEnabled, retrieveEnabled); } //------------------------------------------------------------------------------ diff --git a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp index cb3c97b9d5..66900d5fa0 100644 --- a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp @@ -114,6 +114,7 @@ class ctkDICOMSeriesItemWidgetPrivate : public Ui_ctkDICOMSeriesItemWidget bool RetrieveSeries; bool IsLoaded; bool IsVisible; + bool ThumbnailIsGenerating; int ThumbnailSizePixel; int NumberOfDownloads; QImage ThumbnailImage; @@ -136,6 +137,7 @@ ctkDICOMSeriesItemWidgetPrivate::ctkDICOMSeriesItemWidgetPrivate(ctkDICOMSeriesI this->IsVisible = false; this->StopJobs = false; this->RaiseJobsPriority = false; + this->ThumbnailIsGenerating = false; this->ThumbnailSizePixel = 200; this->NumberOfDownloads = 0; @@ -465,15 +467,25 @@ void ctkDICOMSeriesItemWidgetPrivate::drawThumbnail(const QString& dicomFilePath if (this->ThumbnailImage.isNull()) { QString thumbnailPath = this->DicomDatabase->thumbnailPathForInstance(studyInstanceUID, seriesInstanceUID, sopInstanceUID); - if (thumbnailPath.isEmpty()) + if (thumbnailPath.isEmpty() && !this->ThumbnailIsGenerating) { QColor backgroundColor = this->SeriesThumbnail->palette().color(QPalette::Normal, QPalette::Window); + this->ThumbnailIsGenerating = true; this->Scheduler->generateThumbnail(dicomFilePath, patientID, studyInstanceUID, seriesInstanceUID, sopInstanceUID, modality, backgroundColor, this->RaiseJobsPriority ? QThread::HighestPriority : QThread::HighPriority); return; } + if (thumbnailPath.isEmpty()) + { + return; + } + else + { + this->ThumbnailIsGenerating = false; + } + if (!this->ThumbnailImage.load(thumbnailPath)) { logger.error("drawThumbnail failed, could not load png file. \n"); @@ -953,6 +965,7 @@ void ctkDICOMSeriesItemWidget::onJobStarted(const QVariant &data) if (td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance) { d->ReferenceInstanceInserterJobUID = ""; + d->ThumbnailIsGenerating = false; } else if (td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) { diff --git a/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp index 9aab6b9da8..6bf69d8382 100644 --- a/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp @@ -195,6 +195,11 @@ void ctkDICOMStudyItemWidgetPrivate::createSeries() this->IsGUIUpdating = true; + QSettings settings; + bool queryRetrieveEnabled = settings.value("DICOM/QueryRetrieveEnabled", "").toBool(); + bool queryEnabled = this->QueryOn && queryRetrieveEnabled; + bool retrieveEnabled = this->RetrieveOn && queryRetrieveEnabled; + // Sort by SeriesNumber QMap seriesMap; this->FilteredSeriesCount = 0; @@ -206,7 +211,7 @@ void ctkDICOMStudyItemWidgetPrivate::createSeries() { this->FilteredSeriesCount++; seriesIndex++; - seriesItemWidget->generateInstances(this->QueryOn, this->RetrieveOn); + seriesItemWidget->generateInstances(queryEnabled, retrieveEnabled); continue; } diff --git a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp index 7246c489b2..1755c62b5d 100644 --- a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp @@ -2948,6 +2948,11 @@ void ctkDICOMVisualBrowserWidget::onShowPatients() void ctkDICOMVisualBrowserWidget::onQueryPatients() { Q_D(ctkDICOMVisualBrowserWidget); + if (d->IsGUIUpdating) + { + return; + } + QSettings settings; bool queryRetrieveEnabled = settings.value("DICOM/QueryRetrieveEnabled", "").toBool(); if (!queryRetrieveEnabled) @@ -2956,11 +2961,6 @@ void ctkDICOMVisualBrowserWidget::onQueryPatients() return; } - if (d->IsGUIUpdating) - { - return; - } - if (!d->DicomDatabase) { logger.error("onQueryPatient failed, no DICOM database has been set. \n"); @@ -3110,7 +3110,9 @@ void ctkDICOMVisualBrowserWidget::updateGUIFromScheduler(QList datas) if (updatePatients) { - d->createPatients(); + QSettings settings; + bool queryRetrieveEnabled = settings.value("DICOM/QueryRetrieveEnabled", "").toBool(); + d->createPatients(queryRetrieveEnabled); } }