Преглед изворни кода

Merge pull request #610 from naucoin/3163-export-series-to-file-system

ENH: add the capability to export DICOM to disk
Jean-Christophe Fillion-Robin пре 9 година
родитељ
комит
5f581323b6
2 измењених фајлова са 252 додато и 5 уклоњено
  1. 241 4
      Libs/DICOM/Widgets/ctkDICOMBrowser.cpp
  2. 11 1
      Libs/DICOM/Widgets/ctkDICOMBrowser.h

+ 241 - 4
Libs/DICOM/Widgets/ctkDICOMBrowser.cpp

@@ -75,6 +75,7 @@ public:
   QSharedPointer<ctkDICOMIndexer> DICOMIndexer;
   QProgressDialog *IndexerProgress;
   QProgressDialog *UpdateSchemaProgress;
+  QProgressDialog *ExportProgress;
 
   void showIndexerDialog();
   void showUpdateSchemaDialog();
@@ -99,6 +100,7 @@ ctkDICOMBrowserPrivate::ctkDICOMBrowserPrivate(ctkDICOMBrowser* parent): q_ptr(p
   DICOMIndexer = QSharedPointer<ctkDICOMIndexer> (new ctkDICOMIndexer);
   IndexerProgress = 0;
   UpdateSchemaProgress = 0;
+  ExportProgress = 0;
   DisplayImportSummary = true;
   PatientsAddedDuringImport = 0;
   StudiesAddedDuringImport = 0;
@@ -116,6 +118,10 @@ ctkDICOMBrowserPrivate::~ctkDICOMBrowserPrivate()
     {
     delete UpdateSchemaProgress;
     }
+  if ( ExportProgress )
+    {
+    delete ExportProgress;
+    }
 }
 
 void ctkDICOMBrowserPrivate::showUpdateSchemaDialog()
@@ -669,7 +675,7 @@ void ctkDICOMBrowser::onModelSelected(const QItemSelection &item1, const QItemSe
 }
 
 //----------------------------------------------------------------------------
-bool ctkDICOMBrowser::confirmDeleteSelectedIUDs(QStringList uids)
+bool ctkDICOMBrowser::confirmDeleteSelectedUIDs(QStringList uids)
 {
   Q_D(ctkDICOMBrowser);
 
@@ -753,12 +759,19 @@ void ctkDICOMBrowser::onPatientsRightClicked(const QPoint &point)
 
   patientsMenu->addAction(deleteAction);
 
+  QString exportString = QString("Export ")
+    + QString::number(numPatients)
+    + QString(" selected patients to file system");
+  QAction *exportAction = new QAction(exportString, patientsMenu);
+
+  patientsMenu->addAction(exportAction);
+
   // the table took care of mapping it to a global position so that the
   // menu will pop up at the correct place over this table.
   QAction *selectedAction = patientsMenu->exec(point);
 
   if (selectedAction == deleteAction
-      && this->confirmDeleteSelectedIUDs(selectedPatientsUIDs))
+      && this->confirmDeleteSelectedUIDs(selectedPatientsUIDs))
     {
     qDebug() << "Deleting " << numPatients << " patients";
     foreach (const QString& uid, selectedPatientsUIDs)
@@ -766,6 +779,21 @@ void ctkDICOMBrowser::onPatientsRightClicked(const QPoint &point)
       d->DICOMDatabase->removePatient(uid);
       }
     }
+  else if (selectedAction == exportAction)
+    {
+    ctkFileDialog* directoryDialog = new ctkFileDialog();
+    directoryDialog->setOption(QFileDialog::DontUseNativeDialog);
+    directoryDialog->setOption(QFileDialog::ShowDirsOnly);
+    directoryDialog->setFileMode(QFileDialog::DirectoryOnly);
+    bool res = directoryDialog->exec();
+    if (res)
+      {
+      QStringList dirs = directoryDialog->selectedFiles();
+      QString dirPath = dirs[0];
+      this->exportSelectedPatients(dirPath, selectedPatientsUIDs);
+      }
+    delete directoryDialog;
+    }
 }
 
 //----------------------------------------------------------------------------
@@ -791,18 +819,40 @@ void ctkDICOMBrowser::onStudiesRightClicked(const QPoint &point)
 
   studiesMenu->addAction(deleteAction);
 
+  QString exportString = QString("Export ")
+    + QString::number(numStudies)
+    + QString(" selected studies to file system");
+  QAction *exportAction = new QAction(exportString, studiesMenu);
+
+  studiesMenu->addAction(exportAction);
+
   // the table took care of mapping it to a global position so that the
   // menu will pop up at the correct place over this table.
   QAction *selectedAction = studiesMenu->exec(point);
 
   if (selectedAction == deleteAction
-      && this->confirmDeleteSelectedIUDs(selectedStudiesUIDs))
+      && this->confirmDeleteSelectedUIDs(selectedStudiesUIDs))
     {
     foreach (const QString& uid, selectedStudiesUIDs)
       {
       d->DICOMDatabase->removeStudy(uid);
       }
     }
+  else if (selectedAction == exportAction)
+    {
+    ctkFileDialog* directoryDialog = new ctkFileDialog();
+    directoryDialog->setOption(QFileDialog::DontUseNativeDialog);
+    directoryDialog->setOption(QFileDialog::ShowDirsOnly);
+    directoryDialog->setFileMode(QFileDialog::DirectoryOnly);
+    bool res = directoryDialog->exec();
+    if (res)
+      {
+      QStringList dirs = directoryDialog->selectedFiles();
+      QString dirPath = dirs[0];
+      this->exportSelectedStudies(dirPath, selectedStudiesUIDs);
+      }
+    delete directoryDialog;
+    }
 }
 
 //----------------------------------------------------------------------------
@@ -828,16 +878,203 @@ void ctkDICOMBrowser::onSeriesRightClicked(const QPoint &point)
 
   seriesMenu->addAction(deleteAction);
 
+  QString exportString = QString("Export ")
+    + QString::number(numSeries)
+    + QString(" selected series to file system");
+  QAction *exportAction = new QAction(exportString, seriesMenu);
+  seriesMenu->addAction(exportAction);
+
   // the table took care of mapping it to a global position so that the
   // menu will pop up at the correct place over this table.
   QAction *selectedAction = seriesMenu->exec(point);
 
   if (selectedAction == deleteAction
-      && this->confirmDeleteSelectedIUDs(selectedSeriesUIDs))
+      && this->confirmDeleteSelectedUIDs(selectedSeriesUIDs))
     {
     foreach (const QString& uid, selectedSeriesUIDs)
       {
       d->DICOMDatabase->removeSeries(uid);
       }
     }
+  else if (selectedAction == exportAction)
+    {
+    ctkFileDialog* directoryDialog = new ctkFileDialog();
+    directoryDialog->setOption(QFileDialog::DontUseNativeDialog);
+    directoryDialog->setOption(QFileDialog::ShowDirsOnly);
+    directoryDialog->setFileMode(QFileDialog::DirectoryOnly);
+    bool res = directoryDialog->exec();
+    if (res)
+      {
+      QStringList dirs = directoryDialog->selectedFiles();
+      QString dirPath = dirs[0];
+      this->exportSelectedSeries(dirPath, selectedSeriesUIDs);
+      }
+    delete directoryDialog;
+    }
+}
+
+//----------------------------------------------------------------------------
+void ctkDICOMBrowser::exportSelectedSeries(QString dirPath, QStringList uids)
+{
+  Q_D(ctkDICOMBrowser);
+
+  foreach (const QString& uid, uids)
+    {
+    QStringList filesForSeries = d->DICOMDatabase->filesForSeries(uid);
+
+    // Use the first file to get the overall series information
+    QString firstFilePath = filesForSeries[0];
+    QHash<QString,QString> descriptions (d->DICOMDatabase->descriptionsForFile(firstFilePath));
+    QString patientName = descriptions["PatientsName"];
+    QString patientIDTag = QString("0010,0020");
+    QString patientID = d->DICOMDatabase->fileValue(firstFilePath, patientIDTag);
+    QString studyDescription = descriptions["StudyDescription"];
+    QString seriesDescription = descriptions["SeriesDescription"];
+    QString studyDateTag = QString("0008,0020");
+    QString studyDate = d->DICOMDatabase->fileValue(firstFilePath,studyDateTag);
+    QString seriesNumberTag = QString("0020,0011");
+    QString seriesNumber = d->DICOMDatabase->fileValue(firstFilePath,seriesNumberTag);
+
+    QString sep = "/";
+    QString nameSep = "-";
+    QString destinationDir = dirPath + sep + patientID;
+    if (!patientName.isEmpty())
+      {
+      destinationDir += nameSep + patientName;
+      }
+    destinationDir += sep + studyDate;
+    if (!studyDescription.isEmpty())
+      {
+      destinationDir += nameSep + studyDescription;
+      }
+    destinationDir += sep + seriesNumber;
+    if (!seriesDescription.isEmpty())
+      {
+      destinationDir += nameSep + seriesDescription;
+      }
+    destinationDir += sep;
+
+    // make sure only ascii characters are in the directory path
+    destinationDir = destinationDir.toLatin1();
+    // replace any question marks that were used as replacements for non ascii
+    // characters with underscore
+    destinationDir.replace("?", "_");
+
+    // create the destination directory if necessary
+    if (!QDir().exists(destinationDir))
+      {
+      if (!QDir().mkpath(destinationDir))
+        {
+        QString errorString =
+          QString("Unable to create export destination directory:\n\n")
+          + destinationDir
+          + QString("\n\nHalting export.");
+        ctkMessageBox createDirectoryErrorMessageBox;
+        createDirectoryErrorMessageBox.setText(errorString);
+        createDirectoryErrorMessageBox.setIcon(QMessageBox::Warning);
+        createDirectoryErrorMessageBox.exec();
+        return;
+        }
+      }
+
+    // show progress
+    if (d->ExportProgress == 0)
+      {
+      d->ExportProgress = new QProgressDialog(this->tr("DICOM Export"), "Close", 0, 100, this, Qt::WindowTitleHint | Qt::WindowSystemMenuHint);
+      d->ExportProgress->setWindowModality(Qt::ApplicationModal);
+      d->ExportProgress->setMinimumDuration(0);
+      }
+    QLabel *exportLabel = new QLabel(this->tr("Exporting series ") + seriesNumber);
+    d->ExportProgress->setLabel(exportLabel);
+    d->ExportProgress->setValue(0);
+
+    int fileNumber = 0;
+    int numFiles = filesForSeries.size();
+    d->ExportProgress->setMaximum(numFiles);
+    foreach (const QString& filePath, filesForSeries)
+      {
+      QString destinationFileName = destinationDir;
+
+      QString fileNumberString;
+      // sequentially number the files
+      fileNumberString.sprintf("%06d", fileNumber);
+
+      destinationFileName += fileNumberString + QString(".dcm");
+
+      // replace non ASCII characters
+      destinationFileName = destinationFileName.toLatin1();
+      // replace any question marks that were used as replacements for non ascii
+      // characters with underscore
+      destinationFileName.replace("?", "_");
+
+      if (!QFile::exists(filePath))
+        {
+        d->ExportProgress->setValue(numFiles);
+        QString errorString = QString("Export source file not found:\n\n")
+          + filePath
+          + QString("\n\nHalting export.\n\nError may be fixed via Repair.");
+        ctkMessageBox copyErrorMessageBox;
+        copyErrorMessageBox.setText(errorString);
+        copyErrorMessageBox.setIcon(QMessageBox::Warning);
+        copyErrorMessageBox.exec();
+        return;
+      }
+      if (QFile::exists(destinationFileName))
+        {
+        d->ExportProgress->setValue(numFiles);
+        QString errorString = QString("Export destination file already exists:\n\n")
+          + destinationFileName
+          + QString("\n\nHalting export.");
+        ctkMessageBox copyErrorMessageBox;
+        copyErrorMessageBox.setText(errorString);
+        copyErrorMessageBox.setIcon(QMessageBox::Warning);
+        copyErrorMessageBox.exec();
+        return;
+        }
+
+      bool copyResult = QFile::copy(filePath, destinationFileName);
+      if (!copyResult)
+        {
+        d->ExportProgress->setValue(numFiles);
+        QString errorString = QString("Failed to copy\n\n")
+          + filePath
+          + QString("\n\nto\n\n")
+          + destinationFileName
+          + QString("\n\nHalting export.");
+        ctkMessageBox copyErrorMessageBox;
+        copyErrorMessageBox.setText(errorString);
+        copyErrorMessageBox.setIcon(QMessageBox::Warning);
+        copyErrorMessageBox.exec();
+        return;
+        }
+
+      fileNumber++;
+      d->ExportProgress->setValue(fileNumber);
+      }
+    d->ExportProgress->setValue(numFiles);
+    }
+}
+
+//----------------------------------------------------------------------------
+void ctkDICOMBrowser::exportSelectedStudies(QString dirPath, QStringList uids)
+{
+  Q_D(ctkDICOMBrowser);
+
+  foreach (const QString& uid, uids)
+    {
+    QStringList seriesUIDs = d->DICOMDatabase->seriesForStudy(uid);
+    this->exportSelectedSeries(dirPath, seriesUIDs);
+    }
+}
+
+//----------------------------------------------------------------------------
+void ctkDICOMBrowser::exportSelectedPatients(QString dirPath, QStringList uids)
+{
+  Q_D(ctkDICOMBrowser);
+
+  foreach (const QString& uid, uids)
+    {
+    QStringList studiesUIDs = d->DICOMDatabase->studiesForPatient(uid);
+    this->exportSelectedStudies(dirPath, studiesUIDs);
+    }
 }

+ 11 - 1
Libs/DICOM/Widgets/ctkDICOMBrowser.h

@@ -120,7 +120,7 @@ protected:
     /// empty, uses the UID.
     /// Returns true if the user confirms the delete, false otherwise.
     /// Remembers if the user doesn't want to show the confirmation again.
-    bool confirmDeleteSelectedIUDs(QStringList uids);
+    bool confirmDeleteSelectedUIDs(QStringList uids);
 
 protected Q_SLOTS:
     void onModelSelected(const QItemSelection&, const QItemSelection&);
@@ -134,6 +134,16 @@ protected Q_SLOTS:
     /// Called when a right mouse click is made in the series table
     void onSeriesRightClicked(const QPoint &point);
 
+    /// Called to export the series associated with the selected UIDs
+    /// \sa exportSelectedStudies, exportSelectedPatients
+    void exportSelectedSeries(QString dirPath, QStringList uids);
+    /// Called to export the studies associated with the selected UIDs
+    /// \sa exportSelectedSeries, exportSelectedPatients
+    void exportSelectedStudies(QString dirPath, QStringList uids);
+    /// Called to export the patients associated with the selected UIDs
+    /// \sa exportSelectedStudies, exportSelectedSeries
+    void exportSelectedPatients(QString dirPath, QStringList uids);
+
     /// To be called when dialog finishes
     void onQueryRetrieveFinished();