@@ -75,6 +75,7 @@ class ctkDICOMBrowserPrivate: public Ui_ctkDICOMBrowser
7575 QSharedPointer<ctkDICOMIndexer> DICOMIndexer;
7676 QProgressDialog *IndexerProgress;
7777 QProgressDialog *UpdateSchemaProgress;
78+ QProgressDialog *ExportProgress;
7879
7980 void showIndexerDialog ();
8081 void showUpdateSchemaDialog ();
@@ -99,6 +100,7 @@ ctkDICOMBrowserPrivate::ctkDICOMBrowserPrivate(ctkDICOMBrowser* parent): q_ptr(p
99100 DICOMIndexer = QSharedPointer<ctkDICOMIndexer> (new ctkDICOMIndexer);
100101 IndexerProgress = 0 ;
101102 UpdateSchemaProgress = 0 ;
103+ ExportProgress = 0 ;
102104 DisplayImportSummary = true ;
103105 PatientsAddedDuringImport = 0 ;
104106 StudiesAddedDuringImport = 0 ;
@@ -116,6 +118,10 @@ ctkDICOMBrowserPrivate::~ctkDICOMBrowserPrivate()
116118 {
117119 delete UpdateSchemaProgress;
118120 }
121+ if ( ExportProgress )
122+ {
123+ delete ExportProgress;
124+ }
119125}
120126
121127void ctkDICOMBrowserPrivate::showUpdateSchemaDialog ()
@@ -669,7 +675,7 @@ void ctkDICOMBrowser::onModelSelected(const QItemSelection &item1, const QItemSe
669675}
670676
671677// ----------------------------------------------------------------------------
672- bool ctkDICOMBrowser::confirmDeleteSelectedIUDs (QStringList uids)
678+ bool ctkDICOMBrowser::confirmDeleteSelectedUIDs (QStringList uids)
673679{
674680 Q_D (ctkDICOMBrowser);
675681
@@ -753,19 +759,41 @@ void ctkDICOMBrowser::onPatientsRightClicked(const QPoint &point)
753759
754760 patientsMenu->addAction (deleteAction);
755761
762+ QString exportString = QString (" Export " )
763+ + QString::number (numPatients)
764+ + QString (" selected patients to file system" );
765+ QAction *exportAction = new QAction (exportString, patientsMenu);
766+
767+ patientsMenu->addAction (exportAction);
768+
756769 // the table took care of mapping it to a global position so that the
757770 // menu will pop up at the correct place over this table.
758771 QAction *selectedAction = patientsMenu->exec (point);
759772
760773 if (selectedAction == deleteAction
761- && this ->confirmDeleteSelectedIUDs (selectedPatientsUIDs))
774+ && this ->confirmDeleteSelectedUIDs (selectedPatientsUIDs))
762775 {
763776 qDebug () << " Deleting " << numPatients << " patients" ;
764777 foreach (const QString& uid, selectedPatientsUIDs)
765778 {
766779 d->DICOMDatabase ->removePatient (uid);
767780 }
768781 }
782+ else if (selectedAction == exportAction)
783+ {
784+ ctkFileDialog* directoryDialog = new ctkFileDialog ();
785+ directoryDialog->setOption (QFileDialog::DontUseNativeDialog);
786+ directoryDialog->setOption (QFileDialog::ShowDirsOnly);
787+ directoryDialog->setFileMode (QFileDialog::DirectoryOnly);
788+ bool res = directoryDialog->exec ();
789+ if (res)
790+ {
791+ QStringList dirs = directoryDialog->selectedFiles ();
792+ QString dirPath = dirs[0 ];
793+ this ->exportSelectedPatients (dirPath, selectedPatientsUIDs);
794+ }
795+ delete directoryDialog;
796+ }
769797}
770798
771799// ----------------------------------------------------------------------------
@@ -791,18 +819,40 @@ void ctkDICOMBrowser::onStudiesRightClicked(const QPoint &point)
791819
792820 studiesMenu->addAction (deleteAction);
793821
822+ QString exportString = QString (" Export " )
823+ + QString::number (numStudies)
824+ + QString (" selected studies to file system" );
825+ QAction *exportAction = new QAction (exportString, studiesMenu);
826+
827+ studiesMenu->addAction (exportAction);
828+
794829 // the table took care of mapping it to a global position so that the
795830 // menu will pop up at the correct place over this table.
796831 QAction *selectedAction = studiesMenu->exec (point);
797832
798833 if (selectedAction == deleteAction
799- && this ->confirmDeleteSelectedIUDs (selectedStudiesUIDs))
834+ && this ->confirmDeleteSelectedUIDs (selectedStudiesUIDs))
800835 {
801836 foreach (const QString& uid, selectedStudiesUIDs)
802837 {
803838 d->DICOMDatabase ->removeStudy (uid);
804839 }
805840 }
841+ else if (selectedAction == exportAction)
842+ {
843+ ctkFileDialog* directoryDialog = new ctkFileDialog ();
844+ directoryDialog->setOption (QFileDialog::DontUseNativeDialog);
845+ directoryDialog->setOption (QFileDialog::ShowDirsOnly);
846+ directoryDialog->setFileMode (QFileDialog::DirectoryOnly);
847+ bool res = directoryDialog->exec ();
848+ if (res)
849+ {
850+ QStringList dirs = directoryDialog->selectedFiles ();
851+ QString dirPath = dirs[0 ];
852+ this ->exportSelectedStudies (dirPath, selectedStudiesUIDs);
853+ }
854+ delete directoryDialog;
855+ }
806856}
807857
808858// ----------------------------------------------------------------------------
@@ -828,16 +878,203 @@ void ctkDICOMBrowser::onSeriesRightClicked(const QPoint &point)
828878
829879 seriesMenu->addAction (deleteAction);
830880
881+ QString exportString = QString (" Export " )
882+ + QString::number (numSeries)
883+ + QString (" selected series to file system" );
884+ QAction *exportAction = new QAction (exportString, seriesMenu);
885+ seriesMenu->addAction (exportAction);
886+
831887 // the table took care of mapping it to a global position so that the
832888 // menu will pop up at the correct place over this table.
833889 QAction *selectedAction = seriesMenu->exec (point);
834890
835891 if (selectedAction == deleteAction
836- && this ->confirmDeleteSelectedIUDs (selectedSeriesUIDs))
892+ && this ->confirmDeleteSelectedUIDs (selectedSeriesUIDs))
837893 {
838894 foreach (const QString& uid, selectedSeriesUIDs)
839895 {
840896 d->DICOMDatabase ->removeSeries (uid);
841897 }
842898 }
899+ else if (selectedAction == exportAction)
900+ {
901+ ctkFileDialog* directoryDialog = new ctkFileDialog ();
902+ directoryDialog->setOption (QFileDialog::DontUseNativeDialog);
903+ directoryDialog->setOption (QFileDialog::ShowDirsOnly);
904+ directoryDialog->setFileMode (QFileDialog::DirectoryOnly);
905+ bool res = directoryDialog->exec ();
906+ if (res)
907+ {
908+ QStringList dirs = directoryDialog->selectedFiles ();
909+ QString dirPath = dirs[0 ];
910+ this ->exportSelectedSeries (dirPath, selectedSeriesUIDs);
911+ }
912+ delete directoryDialog;
913+ }
914+ }
915+
916+ // ----------------------------------------------------------------------------
917+ void ctkDICOMBrowser::exportSelectedSeries (QString dirPath, QStringList uids)
918+ {
919+ Q_D (ctkDICOMBrowser);
920+
921+ foreach (const QString& uid, uids)
922+ {
923+ QStringList filesForSeries = d->DICOMDatabase ->filesForSeries (uid);
924+
925+ // Use the first file to get the overall series information
926+ QString firstFilePath = filesForSeries[0 ];
927+ QHash<QString,QString> descriptions (d->DICOMDatabase ->descriptionsForFile (firstFilePath));
928+ QString patientName = descriptions[" PatientsName" ];
929+ QString patientIDTag = QString (" 0010,0020" );
930+ QString patientID = d->DICOMDatabase ->fileValue (firstFilePath, patientIDTag);
931+ QString studyDescription = descriptions[" StudyDescription" ];
932+ QString seriesDescription = descriptions[" SeriesDescription" ];
933+ QString studyDateTag = QString (" 0008,0020" );
934+ QString studyDate = d->DICOMDatabase ->fileValue (firstFilePath,studyDateTag);
935+ QString seriesNumberTag = QString (" 0020,0011" );
936+ QString seriesNumber = d->DICOMDatabase ->fileValue (firstFilePath,seriesNumberTag);
937+
938+ QString sep = " /" ;
939+ QString nameSep = " -" ;
940+ QString destinationDir = dirPath + sep + patientID;
941+ if (!patientName.isEmpty ())
942+ {
943+ destinationDir += nameSep + patientName;
944+ }
945+ destinationDir += sep + studyDate;
946+ if (!studyDescription.isEmpty ())
947+ {
948+ destinationDir += nameSep + studyDescription;
949+ }
950+ destinationDir += sep + seriesNumber;
951+ if (!seriesDescription.isEmpty ())
952+ {
953+ destinationDir += nameSep + seriesDescription;
954+ }
955+ destinationDir += sep;
956+
957+ // make sure only ascii characters are in the directory path
958+ destinationDir = destinationDir.toLatin1 ();
959+ // replace any question marks that were used as replacements for non ascii
960+ // characters with underscore
961+ destinationDir.replace (" ?" , " _" );
962+
963+ // create the destination directory if necessary
964+ if (!QDir ().exists (destinationDir))
965+ {
966+ if (!QDir ().mkpath (destinationDir))
967+ {
968+ QString errorString =
969+ QString (" Unable to create export destination directory:\n\n " )
970+ + destinationDir
971+ + QString (" \n\n Halting export." );
972+ ctkMessageBox createDirectoryErrorMessageBox;
973+ createDirectoryErrorMessageBox.setText (errorString);
974+ createDirectoryErrorMessageBox.setIcon (QMessageBox::Warning);
975+ createDirectoryErrorMessageBox.exec ();
976+ return ;
977+ }
978+ }
979+
980+ // show progress
981+ if (d->ExportProgress == 0 )
982+ {
983+ d->ExportProgress = new QProgressDialog (this ->tr (" DICOM Export" ), " Close" , 0 , 100 , this , Qt::WindowTitleHint | Qt::WindowSystemMenuHint);
984+ d->ExportProgress ->setWindowModality (Qt::ApplicationModal);
985+ d->ExportProgress ->setMinimumDuration (0 );
986+ }
987+ QLabel *exportLabel = new QLabel (this ->tr (" Exporting series " ) + seriesNumber);
988+ d->ExportProgress ->setLabel (exportLabel);
989+ d->ExportProgress ->setValue (0 );
990+
991+ int fileNumber = 0 ;
992+ int numFiles = filesForSeries.size ();
993+ d->ExportProgress ->setMaximum (numFiles);
994+ foreach (const QString& filePath, filesForSeries)
995+ {
996+ QString destinationFileName = destinationDir;
997+
998+ QString fileNumberString;
999+ // sequentially number the files
1000+ fileNumberString.sprintf (" %06d" , fileNumber);
1001+
1002+ destinationFileName += fileNumberString + QString (" .dcm" );
1003+
1004+ // replace non ASCII characters
1005+ destinationFileName = destinationFileName.toLatin1 ();
1006+ // replace any question marks that were used as replacements for non ascii
1007+ // characters with underscore
1008+ destinationFileName.replace (" ?" , " _" );
1009+
1010+ if (!QFile::exists (filePath))
1011+ {
1012+ d->ExportProgress ->setValue (numFiles);
1013+ QString errorString = QString (" Export source file not found:\n\n " )
1014+ + filePath
1015+ + QString (" \n\n Halting export.\n\n Error may be fixed via Repair." );
1016+ ctkMessageBox copyErrorMessageBox;
1017+ copyErrorMessageBox.setText (errorString);
1018+ copyErrorMessageBox.setIcon (QMessageBox::Warning);
1019+ copyErrorMessageBox.exec ();
1020+ return ;
1021+ }
1022+ if (QFile::exists (destinationFileName))
1023+ {
1024+ d->ExportProgress ->setValue (numFiles);
1025+ QString errorString = QString (" Export destination file already exists:\n\n " )
1026+ + destinationFileName
1027+ + QString (" \n\n Halting export." );
1028+ ctkMessageBox copyErrorMessageBox;
1029+ copyErrorMessageBox.setText (errorString);
1030+ copyErrorMessageBox.setIcon (QMessageBox::Warning);
1031+ copyErrorMessageBox.exec ();
1032+ return ;
1033+ }
1034+
1035+ bool copyResult = QFile::copy (filePath, destinationFileName);
1036+ if (!copyResult)
1037+ {
1038+ d->ExportProgress ->setValue (numFiles);
1039+ QString errorString = QString (" Failed to copy\n\n " )
1040+ + filePath
1041+ + QString (" \n\n to\n\n " )
1042+ + destinationFileName
1043+ + QString (" \n\n Halting export." );
1044+ ctkMessageBox copyErrorMessageBox;
1045+ copyErrorMessageBox.setText (errorString);
1046+ copyErrorMessageBox.setIcon (QMessageBox::Warning);
1047+ copyErrorMessageBox.exec ();
1048+ return ;
1049+ }
1050+
1051+ fileNumber++;
1052+ d->ExportProgress ->setValue (fileNumber);
1053+ }
1054+ d->ExportProgress ->setValue (numFiles);
1055+ }
1056+ }
1057+
1058+ // ----------------------------------------------------------------------------
1059+ void ctkDICOMBrowser::exportSelectedStudies (QString dirPath, QStringList uids)
1060+ {
1061+ Q_D (ctkDICOMBrowser);
1062+
1063+ foreach (const QString& uid, uids)
1064+ {
1065+ QStringList seriesUIDs = d->DICOMDatabase ->seriesForStudy (uid);
1066+ this ->exportSelectedSeries (dirPath, seriesUIDs);
1067+ }
1068+ }
1069+
1070+ // ----------------------------------------------------------------------------
1071+ void ctkDICOMBrowser::exportSelectedPatients (QString dirPath, QStringList uids)
1072+ {
1073+ Q_D (ctkDICOMBrowser);
1074+
1075+ foreach (const QString& uid, uids)
1076+ {
1077+ QStringList studiesUIDs = d->DICOMDatabase ->studiesForPatient (uid);
1078+ this ->exportSelectedStudies (dirPath, studiesUIDs);
1079+ }
8431080}
0 commit comments