Skip to content

Commit 8cad501

Browse files
committed
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.
1 parent 7c5c6ee commit 8cad501

File tree

9 files changed

+151
-2
lines changed

9 files changed

+151
-2
lines changed

Base/QTCore/qSlicerCoreIOManager.cxx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,3 +781,19 @@ void qSlicerCoreIOManager::setDefaultSceneFileType(QString fileType)
781781
Q_D(qSlicerCoreIOManager);
782782
d->DefaultSceneFileType = fileType;
783783
}
784+
785+
//-----------------------------------------------------------------------------
786+
bool qSlicerCoreIOManager::examineFileInfoList(QFileInfoList &fileInfoList, QFileInfo &archetypeFileInfo, QString &readerDescription, qSlicerIO::IOProperties &ioProperties)const
787+
{
788+
Q_D(const qSlicerCoreIOManager);
789+
QList<qSlicerFileReader*> res;
790+
foreach(qSlicerFileReader* reader, d->Readers)
791+
{
792+
if (reader->examineFileInfoList(fileInfoList, archetypeFileInfo, ioProperties))
793+
{
794+
readerDescription = reader->description();
795+
return (true);
796+
}
797+
}
798+
return (false);
799+
}

Base/QTCore/qSlicerCoreIOManager.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222
#define __qSlicerCoreIOManager_h
2323

2424
// Qt includes
25-
#include <QList>
25+
#include <QFileInfo>
2626
#include <QImage>
27+
#include <QList>
2728
#include <QMap>
2829
#include <QObject>
2930
#include <QVariantMap>
@@ -170,6 +171,21 @@ class Q_SLICER_BASE_QTCORE_EXPORT qSlicerCoreIOManager:public QObject
170171
/// Defines the file format that should be offered by default when the scene is saved.
171172
Q_INVOKABLE QString defaultSceneFileType()const;
172173

174+
/// Iterates through readers looking at the fileInfoList to see if there is an entry that can serve as
175+
/// an archetype for loading multiple fileInfos. If so, the reader removes the recognized
176+
/// fileInfos from the list and sets the ioProperties so that the corresponding
177+
/// loader will read these files. The archetypeEntry will contain the fileInfo
178+
/// for the archetype and the method returns true. If no pattern is recognized
179+
/// the method returns false.
180+
/// The specific motivating use case is when the file
181+
/// list contains a set of related files, such as a list of image files that
182+
/// are recognized as a volume. But other cases could also make sense, such as when
183+
/// a file format has a set or related files such as textures or material files
184+
/// for a surface model.
185+
/// \sa qSlicerDataDialog
186+
/// \sa qSlicerFileReader
187+
Q_INVOKABLE bool examineFileInfoList(QFileInfoList &fileInfoList, QFileInfo &archetypeEntry, QString &readerDescription, qSlicerIO::IOProperties &ioProperties)const;
188+
173189
public slots:
174190

175191
/// Defines the file format that should be offered by default when the scene is saved.

Base/QTCore/qSlicerFileReader.cxx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,9 @@ QStringList qSlicerFileReader::loadedNodes()const
119119
Q_D(const qSlicerFileReader);
120120
return d->LoadedNodes;
121121
}
122+
123+
//----------------------------------------------------------------------------
124+
bool qSlicerFileReader::examineFileInfoList(QFileInfoList &fileInfoList, QFileInfo &archetypeFileInfo, qSlicerIO::IOProperties &ioProperties)const
125+
{
126+
return(false);
127+
}

Base/QTCore/qSlicerFileReader.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
#ifndef __qSlicerFileReader_h
2222
#define __qSlicerFileReader_h
2323

24+
// Qt includes
25+
#include <QFileInfo>
26+
2427
// QtCore includes
2528
#include "qSlicerIO.h"
2629
#include "qSlicerBaseQTCoreExport.h"
@@ -64,6 +67,11 @@ class Q_SLICER_BASE_QTCORE_EXPORT qSlicerFileReader
6467
/// \sa setLoadedNodes(), load()
6568
QStringList loadedNodes()const;
6669

70+
/// Implements the file list examination for the corresponding method in the core
71+
/// IO manager.
72+
/// \sa qSlicerCoreIOManager
73+
virtual bool examineFileInfoList(QFileInfoList &fileInfoList, QFileInfo &archetypeFileInfo, qSlicerIO::IOProperties &ioProperties)const;
74+
6775
protected:
6876
/// Must be called in load() on success with the list of nodes added into the
6977
/// scene.

Base/QTGUI/qSlicerDataDialog.cxx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,43 @@ void qSlicerDataDialogPrivate::addDirectory(const QDir& directory)
163163
bool recursive = true;
164164
QDir::Filters filters =
165165
QDir::AllDirs | QDir::Files | QDir::Readable | QDir::NoDotAndDotDot;
166-
foreach(QFileInfo entry, directory.entryInfoList(filters))
166+
QFileInfoList fileInfoList = directory.entryInfoList(filters);
167+
168+
//
169+
// check to see if any readers recognize the directory contents
170+
// and provide an archetype.
171+
//
172+
qSlicerCoreIOManager* coreIOManager =
173+
qSlicerCoreApplication::application()->coreIOManager();
174+
QString readerDescription;
175+
qSlicerIO::IOProperties ioProperties;
176+
QFileInfo archetypeEntry;
177+
if (coreIOManager->examineFileInfoList(fileInfoList, archetypeEntry, readerDescription, ioProperties))
178+
{
179+
this->addFile(archetypeEntry);
180+
QString filePath = archetypeEntry.absoluteFilePath();
181+
QList<QTableWidgetItem *> items = this->FileWidget->findItems(filePath, Qt::MatchExactly);
182+
if (items.isEmpty())
183+
{
184+
qWarning() << "Couldn't add archetype widget for file: " << filePath;
185+
}
186+
else
187+
{
188+
QTableWidgetItem *item = items[0];
189+
QWidget *cellWidget = this->FileWidget->cellWidget(item->row(), TypeColumn);
190+
QComboBox *descriptionComboBox = dynamic_cast<QComboBox *>(cellWidget);
191+
descriptionComboBox->setCurrentIndex(descriptionComboBox->findText(readerDescription));
192+
cellWidget = this->FileWidget->cellWidget(item->row(), OptionsColumn);
193+
qSlicerIOOptionsWidget *ioOptionsWidget = dynamic_cast<qSlicerIOOptionsWidget *> (cellWidget);
194+
ioOptionsWidget->updateGUI(ioProperties);
195+
}
196+
}
197+
198+
//
199+
// now add any files and directories that weren't filtered
200+
// out by the ioManager
201+
//
202+
foreach(QFileInfo entry, fileInfoList)
167203
{
168204
if (entry.isFile())
169205
{

Modules/Loadable/Volumes/qSlicerVolumesIOOptionsWidget.cxx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,14 @@ void qSlicerVolumesIOOptionsWidget::updateColorSelector()
223223
}
224224
}
225225
}
226+
227+
//------------------------------------------------------------------------------
228+
void qSlicerVolumesIOOptionsWidget::updateGUI(const qSlicerIO::IOProperties& ioProperties)
229+
{
230+
Q_D(qSlicerVolumesIOOptionsWidget);
231+
qSlicerIOOptionsWidget::updateGUI(ioProperties);
232+
if (ioProperties.contains("singleFile"))
233+
{
234+
d->SingleFileCheckBox->setChecked(ioProperties["singleFile"].toBool());
235+
}
236+
}

Modules/Loadable/Volumes/qSlicerVolumesIOOptionsWidget.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ class Q_SLICER_QTMODULES_VOLUMES_EXPORT qSlicerVolumesIOOptionsWidget :
4141
qSlicerVolumesIOOptionsWidget(QWidget *parent=0);
4242
virtual ~qSlicerVolumesIOOptionsWidget();
4343

44+
/// Allows custom handling of image sets as volumes
45+
/// \sa qSlicerVolumesReader
46+
/// \sa qSlicerDataDialog::addDirectory
47+
void updateGUI(const qSlicerIO::IOProperties& ioProperties);
48+
4449
public slots:
4550
virtual void setFileName(const QString& fileName);
4651
virtual void setFileNames(const QStringList& fileNames);

Modules/Loadable/Volumes/qSlicerVolumesReader.cxx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@
1818
1919
==============================================================================*/
2020

21+
// std includes
22+
#include <vector>
23+
#include <algorithm>
24+
2125
// Qt includes
26+
#include <QDebug>
2227
#include <QFileInfo>
2328

2429
// SlicerQt includes
@@ -39,6 +44,9 @@
3944
#include <vtkSmartPointer.h>
4045
#include <vtkStringArray.h>
4146

47+
// ITK includes
48+
#include <itkArchetypeSeriesFileNames.h>
49+
4250
//-----------------------------------------------------------------------------
4351
class qSlicerVolumesReaderPrivate
4452
{
@@ -205,3 +213,40 @@ bool qSlicerVolumesReader::load(const IOProperties& properties)
205213
}
206214
return node != 0;
207215
}
216+
217+
//-----------------------------------------------------------------------------
218+
bool qSlicerVolumesReader::examineFileInfoList(QFileInfoList &fileInfoList, QFileInfo &archetypeFileInfo, qSlicerIO::IOProperties &ioProperties)const
219+
{
220+
221+
//
222+
// Check each file to see if it's recognzied as part of a series. If so,
223+
// keep it as the archetype and remove all the others from the list
224+
//
225+
foreach(QFileInfo fileInfo, fileInfoList)
226+
{
227+
itk::ArchetypeSeriesFileNames::Pointer seriesNames = itk::ArchetypeSeriesFileNames::New();
228+
std::vector<std::string> candidateFiles;
229+
seriesNames->SetArchetype(fileInfo.absoluteFilePath().toStdString());
230+
candidateFiles = seriesNames->GetFileNames();
231+
if (candidateFiles.size() > 1)
232+
{
233+
archetypeFileInfo = fileInfo;
234+
QMutableListIterator<QFileInfo> fileInfoIterator(fileInfoList);
235+
while (fileInfoIterator.hasNext())
236+
{
237+
const QString &path = fileInfoIterator.next().absoluteFilePath();
238+
if (path == archetypeFileInfo.absoluteFilePath())
239+
{
240+
continue;
241+
}
242+
if (std::find(candidateFiles.begin(), candidateFiles.end(), path.toStdString()) != candidateFiles.end())
243+
{
244+
fileInfoIterator.remove();
245+
}
246+
}
247+
ioProperties["singleFile"] = false;
248+
return true;
249+
}
250+
}
251+
return false;
252+
}

Modules/Loadable/Volumes/qSlicerVolumesReader.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ class qSlicerVolumesReader
4747
virtual qSlicerIOOptions* options()const;
4848

4949
virtual bool load(const IOProperties& properties);
50+
51+
/// Implements the file list examination for the corresponding method in the core
52+
/// IO manager.
53+
/// \sa qSlicerCoreIOManager
54+
virtual bool examineFileInfoList(QFileInfoList &fileInfoList, QFileInfo &archetypeFileInfo, qSlicerIO::IOProperties &ioProperties)const;
55+
5056
protected:
5157
QScopedPointer<qSlicerVolumesReaderPrivate> d_ptr;
5258

0 commit comments

Comments
 (0)