From 8cad501d46c978068928f490d4827f0a8fcf3719 Mon Sep 17 00:00:00 2001 From: Steve Pieper Date: Fri, 8 Feb 2019 18:46:58 -0500 Subject: [PATCH] ENH: adds hook for loading directory archetypes When a directory is added via the Add Data dialog, it may contain a collection of files that should be treated as a single MRML data type, such as a series of image files that get loaded as a volume. This change allows a qSlicerFileReader subclass to define a method that can examine the contents of a directory and filter out any files that should be loaded as a group. One of these files then serves as the archetype and the io properties can be configured such that the files are loaded correctly. The qSlicerVolumesReader and related classes have been updated to use the hooks for reading multiple image files as a volume by default. --- Base/QTCore/qSlicerCoreIOManager.cxx | 16 +++++++ Base/QTCore/qSlicerCoreIOManager.h | 18 +++++++- Base/QTCore/qSlicerFileReader.cxx | 6 +++ Base/QTCore/qSlicerFileReader.h | 8 ++++ Base/QTGUI/qSlicerDataDialog.cxx | 38 +++++++++++++++- .../Volumes/qSlicerVolumesIOOptionsWidget.cxx | 11 +++++ .../Volumes/qSlicerVolumesIOOptionsWidget.h | 5 +++ .../Loadable/Volumes/qSlicerVolumesReader.cxx | 45 +++++++++++++++++++ .../Loadable/Volumes/qSlicerVolumesReader.h | 6 +++ 9 files changed, 151 insertions(+), 2 deletions(-) diff --git a/Base/QTCore/qSlicerCoreIOManager.cxx b/Base/QTCore/qSlicerCoreIOManager.cxx index aa3801771..bbadb9017 100644 --- a/Base/QTCore/qSlicerCoreIOManager.cxx +++ b/Base/QTCore/qSlicerCoreIOManager.cxx @@ -781,3 +781,19 @@ void qSlicerCoreIOManager::setDefaultSceneFileType(QString fileType) Q_D(qSlicerCoreIOManager); d->DefaultSceneFileType = fileType; } + +//----------------------------------------------------------------------------- +bool qSlicerCoreIOManager::examineFileInfoList(QFileInfoList &fileInfoList, QFileInfo &archetypeFileInfo, QString &readerDescription, qSlicerIO::IOProperties &ioProperties)const +{ + Q_D(const qSlicerCoreIOManager); + QList res; + foreach(qSlicerFileReader* reader, d->Readers) + { + if (reader->examineFileInfoList(fileInfoList, archetypeFileInfo, ioProperties)) + { + readerDescription = reader->description(); + return (true); + } + } + return (false); +} diff --git a/Base/QTCore/qSlicerCoreIOManager.h b/Base/QTCore/qSlicerCoreIOManager.h index d646a6314..bbd6f7858 100644 --- a/Base/QTCore/qSlicerCoreIOManager.h +++ b/Base/QTCore/qSlicerCoreIOManager.h @@ -22,8 +22,9 @@ #define __qSlicerCoreIOManager_h // Qt includes -#include +#include #include +#include #include #include #include @@ -170,6 +171,21 @@ public: /// Defines the file format that should be offered by default when the scene is saved. Q_INVOKABLE QString defaultSceneFileType()const; + /// Iterates through readers looking at the fileInfoList to see if there is an entry that can serve as + /// an archetype for loading multiple fileInfos. If so, the reader removes the recognized + /// fileInfos from the list and sets the ioProperties so that the corresponding + /// loader will read these files. The archetypeEntry will contain the fileInfo + /// for the archetype and the method returns true. If no pattern is recognized + /// the method returns false. + /// The specific motivating use case is when the file + /// list contains a set of related files, such as a list of image files that + /// are recognized as a volume. But other cases could also make sense, such as when + /// a file format has a set or related files such as textures or material files + /// for a surface model. + /// \sa qSlicerDataDialog + /// \sa qSlicerFileReader + Q_INVOKABLE bool examineFileInfoList(QFileInfoList &fileInfoList, QFileInfo &archetypeEntry, QString &readerDescription, qSlicerIO::IOProperties &ioProperties)const; + public slots: /// Defines the file format that should be offered by default when the scene is saved. diff --git a/Base/QTCore/qSlicerFileReader.cxx b/Base/QTCore/qSlicerFileReader.cxx index 4a39ebe58..c48fd9723 100644 --- a/Base/QTCore/qSlicerFileReader.cxx +++ b/Base/QTCore/qSlicerFileReader.cxx @@ -119,3 +119,9 @@ QStringList qSlicerFileReader::loadedNodes()const Q_D(const qSlicerFileReader); return d->LoadedNodes; } + +//---------------------------------------------------------------------------- +bool qSlicerFileReader::examineFileInfoList(QFileInfoList &fileInfoList, QFileInfo &archetypeFileInfo, qSlicerIO::IOProperties &ioProperties)const +{ + return(false); +} diff --git a/Base/QTCore/qSlicerFileReader.h b/Base/QTCore/qSlicerFileReader.h index b3ca9cfad..2e4d863be 100644 --- a/Base/QTCore/qSlicerFileReader.h +++ b/Base/QTCore/qSlicerFileReader.h @@ -21,6 +21,9 @@ #ifndef __qSlicerFileReader_h #define __qSlicerFileReader_h +// Qt includes +#include + // QtCore includes #include "qSlicerIO.h" #include "qSlicerBaseQTCoreExport.h" @@ -64,6 +67,11 @@ public: /// \sa setLoadedNodes(), load() QStringList loadedNodes()const; + /// Implements the file list examination for the corresponding method in the core + /// IO manager. + /// \sa qSlicerCoreIOManager + virtual bool examineFileInfoList(QFileInfoList &fileInfoList, QFileInfo &archetypeFileInfo, qSlicerIO::IOProperties &ioProperties)const; + protected: /// Must be called in load() on success with the list of nodes added into the /// scene. diff --git a/Base/QTGUI/qSlicerDataDialog.cxx b/Base/QTGUI/qSlicerDataDialog.cxx index 8067217d9..88ba0a083 100644 --- a/Base/QTGUI/qSlicerDataDialog.cxx +++ b/Base/QTGUI/qSlicerDataDialog.cxx @@ -163,7 +163,43 @@ void qSlicerDataDialogPrivate::addDirectory(const QDir& directory) bool recursive = true; QDir::Filters filters = QDir::AllDirs | QDir::Files | QDir::Readable | QDir::NoDotAndDotDot; - foreach(QFileInfo entry, directory.entryInfoList(filters)) + QFileInfoList fileInfoList = directory.entryInfoList(filters); + + // + // check to see if any readers recognize the directory contents + // and provide an archetype. + // + qSlicerCoreIOManager* coreIOManager = + qSlicerCoreApplication::application()->coreIOManager(); + QString readerDescription; + qSlicerIO::IOProperties ioProperties; + QFileInfo archetypeEntry; + if (coreIOManager->examineFileInfoList(fileInfoList, archetypeEntry, readerDescription, ioProperties)) + { + this->addFile(archetypeEntry); + QString filePath = archetypeEntry.absoluteFilePath(); + QList items = this->FileWidget->findItems(filePath, Qt::MatchExactly); + if (items.isEmpty()) + { + qWarning() << "Couldn't add archetype widget for file: " << filePath; + } + else + { + QTableWidgetItem *item = items[0]; + QWidget *cellWidget = this->FileWidget->cellWidget(item->row(), TypeColumn); + QComboBox *descriptionComboBox = dynamic_cast(cellWidget); + descriptionComboBox->setCurrentIndex(descriptionComboBox->findText(readerDescription)); + cellWidget = this->FileWidget->cellWidget(item->row(), OptionsColumn); + qSlicerIOOptionsWidget *ioOptionsWidget = dynamic_cast (cellWidget); + ioOptionsWidget->updateGUI(ioProperties); + } + } + + // + // now add any files and directories that weren't filtered + // out by the ioManager + // + foreach(QFileInfo entry, fileInfoList) { if (entry.isFile()) { diff --git a/Modules/Loadable/Volumes/qSlicerVolumesIOOptionsWidget.cxx b/Modules/Loadable/Volumes/qSlicerVolumesIOOptionsWidget.cxx index 7da12a312..3791e766d 100644 --- a/Modules/Loadable/Volumes/qSlicerVolumesIOOptionsWidget.cxx +++ b/Modules/Loadable/Volumes/qSlicerVolumesIOOptionsWidget.cxx @@ -223,3 +223,14 @@ void qSlicerVolumesIOOptionsWidget::updateColorSelector() } } } + +//------------------------------------------------------------------------------ +void qSlicerVolumesIOOptionsWidget::updateGUI(const qSlicerIO::IOProperties& ioProperties) +{ + Q_D(qSlicerVolumesIOOptionsWidget); + qSlicerIOOptionsWidget::updateGUI(ioProperties); + if (ioProperties.contains("singleFile")) + { + d->SingleFileCheckBox->setChecked(ioProperties["singleFile"].toBool()); + } +} diff --git a/Modules/Loadable/Volumes/qSlicerVolumesIOOptionsWidget.h b/Modules/Loadable/Volumes/qSlicerVolumesIOOptionsWidget.h index 15c12480e..43e82da3a 100644 --- a/Modules/Loadable/Volumes/qSlicerVolumesIOOptionsWidget.h +++ b/Modules/Loadable/Volumes/qSlicerVolumesIOOptionsWidget.h @@ -41,6 +41,11 @@ public: qSlicerVolumesIOOptionsWidget(QWidget *parent=0); virtual ~qSlicerVolumesIOOptionsWidget(); + /// Allows custom handling of image sets as volumes + /// \sa qSlicerVolumesReader + /// \sa qSlicerDataDialog::addDirectory + void updateGUI(const qSlicerIO::IOProperties& ioProperties); + public slots: virtual void setFileName(const QString& fileName); virtual void setFileNames(const QStringList& fileNames); diff --git a/Modules/Loadable/Volumes/qSlicerVolumesReader.cxx b/Modules/Loadable/Volumes/qSlicerVolumesReader.cxx index 50fbd4af4..a4a0664cb 100644 --- a/Modules/Loadable/Volumes/qSlicerVolumesReader.cxx +++ b/Modules/Loadable/Volumes/qSlicerVolumesReader.cxx @@ -18,7 +18,12 @@ ==============================================================================*/ +// std includes +#include +#include + // Qt includes +#include #include // SlicerQt includes @@ -39,6 +44,9 @@ #include #include +// ITK includes +#include + //----------------------------------------------------------------------------- class qSlicerVolumesReaderPrivate { @@ -205,3 +213,40 @@ bool qSlicerVolumesReader::load(const IOProperties& properties) } return node != 0; } + +//----------------------------------------------------------------------------- +bool qSlicerVolumesReader::examineFileInfoList(QFileInfoList &fileInfoList, QFileInfo &archetypeFileInfo, qSlicerIO::IOProperties &ioProperties)const +{ + + // + // Check each file to see if it's recognzied as part of a series. If so, + // keep it as the archetype and remove all the others from the list + // + foreach(QFileInfo fileInfo, fileInfoList) + { + itk::ArchetypeSeriesFileNames::Pointer seriesNames = itk::ArchetypeSeriesFileNames::New(); + std::vector candidateFiles; + seriesNames->SetArchetype(fileInfo.absoluteFilePath().toStdString()); + candidateFiles = seriesNames->GetFileNames(); + if (candidateFiles.size() > 1) + { + archetypeFileInfo = fileInfo; + QMutableListIterator fileInfoIterator(fileInfoList); + while (fileInfoIterator.hasNext()) + { + const QString &path = fileInfoIterator.next().absoluteFilePath(); + if (path == archetypeFileInfo.absoluteFilePath()) + { + continue; + } + if (std::find(candidateFiles.begin(), candidateFiles.end(), path.toStdString()) != candidateFiles.end()) + { + fileInfoIterator.remove(); + } + } + ioProperties["singleFile"] = false; + return true; + } + } + return false; +} diff --git a/Modules/Loadable/Volumes/qSlicerVolumesReader.h b/Modules/Loadable/Volumes/qSlicerVolumesReader.h index 48f6b1d9f..842f5c40a 100644 --- a/Modules/Loadable/Volumes/qSlicerVolumesReader.h +++ b/Modules/Loadable/Volumes/qSlicerVolumesReader.h @@ -47,6 +47,12 @@ public: virtual qSlicerIOOptions* options()const; virtual bool load(const IOProperties& properties); + + /// Implements the file list examination for the corresponding method in the core + /// IO manager. + /// \sa qSlicerCoreIOManager + virtual bool examineFileInfoList(QFileInfoList &fileInfoList, QFileInfo &archetypeFileInfo, qSlicerIO::IOProperties &ioProperties)const; + protected: QScopedPointer d_ptr; -- GitLab