Documentation/Nightly/Developers/IO

From Slicer Wiki
Jump to: navigation, search
Home < Documentation < Nightly < Developers < IO


For the latest Slicer documentation, visit the read-the-docs.


Read

IOOverview.png

Main classes

  • vtkSlicerXYZNode is a storable node that represents the data from file (e.g. [vtkMRMLModelNode)
  • vtkSlicerXYZStorageNode is the file reader. It populates the storable node using the method vtkMRMLXYZStorageNode::ReadData(vtkMRMLStorableNode*,bool).
  • vtkSlicerXYZsLogic is the MRML logic of the XYZ module. It exposes a convenient method AddXYZ(const char* fileName, const char* nodeName=0); that creates a MRML XYZ node (vtkMRMLXYZNode)and its associated storage node (vtkMRMLXYZStorageNode), add them into the scene, and call vtkMRMLXYZStorageNode::ReadData(vtkMRMLStorableNode*,bool); on the storage node to load the file. If the loading fails, it removes the previously created nodes from the scene.
  • qSlicerXYZsIO is a plugin that is registered by modules into the qSlicerCoreIOManager. It is the interface between Qt and MRML logics. It internally calls vtkSlicerXYZsLogic::AddXYZ().
  • qSlicerXYZsIOOptionsWidget is a widget that sets loading options that gets passed to the logic.
  • qSlicerCoreIOManager is the central class where any IO operation must go through. qSlicerIOs can be registered using qSlicerCoreIOManager::registerIO(qSlicerIO*) and nodes can be loaded using qSlicerCoreIOManager::loadNodes(...). It exposes a set of convenient methods such as "what reader must be used for what file".
  • qSlicerDataDialog is the dialog that allows the user to select the files to load.

How to add support for reading a new file format ?

  1. Write method vtkMRMLXYZStorageNode::ReadDataInternal(vtkMRMLStorableNode*, bool temporary);
  2. Write method vtkMRMLXYZsLogic::AddXYZ(const char* fileName, const char nodeName =0);
  3. Write class qSlicerXYZsIO
  4. Optional: Write class qSlicerXYZsIOOptionsWidget if you want the user to optionally specify loading options.
  5. In qSlicerXYZsModule::setup(), instantiate and register qSlicerXYZsIO to qSlicerCoreIOManager
  6. Add file format to [Documentation/Nightly/SlicerApplication/SupportedDataFormat|SupportedDataFormat] wiki page

ModifiedSinceRead

In order to inform the user what data has been changed since it was last read, a ModifiedSinceRead mechanism is in place to track when was the file last read and when was the data last modified. If a modification happened , the storable node vtkMRMLStorableNode::GetModifiedSinceRead() must return true. If the data in the node is the same as in the file, then vtkMRMLStorableNode::GetModifiedSinceRead() must return false.

vtkMRMLStorageNode keeps track of when a file was last read or written (vtkTimeStamp* vtkMRMLStorageNode::StoredTime). vtkMRMLStorableNode keeps track of when the data was last modified (vtkTimeStamp vtkMRMLStorableNode::StorableModifiedTime). Anytime a vtkMRMLStorableNode property that is saved in file is modified, the StorableModifiedTime time stamp must be modified.

At exit time (qSlicerMainWindow::closeEvent()), vtkMRMLStorableNode::GetModifiedSinceRead() is called to check if the data in the node is the same as in the file or if it has been modified after the file was last read or written. It internally checks vtkTimeStamp* vtkMRMLStorageNode::StoredTime and vtkTimeStamp vtkMRMLStorableNode::StorableModifiedTime. If the data is more recent, then a message dialog is shown to the user telling him that some data is different from file; leaving without saving will loose the changes.

Example

How to load files programmatically

C++ Python
qSlicerCoreIOManager* coreIOManager = qSlicerCoreApplication::application()->coreIOManager();
qSlicerIO::IOProperties fileParameters;
fileParameters["filename"] = "/path/of/file.ext";
vtkMRMLNode* volumeNode = coreIOManager->loadNodesAndGetFirst("VolumeFile", parameters);

Works also with "ModelFile", "TransformFile", "SceneFile"...

>>> volumeNode = slicer.util.loadVolume('/path/of/file.ext')

It works also with loadModel, loadTransform, loadScene...

Note that IO dialogs should be used as often as possible.

Write

The mechanism is similar to Read. vtkMRMLXYZStorageNode::WriteDataInternal(...) and qSlicerXYZWriter must implemented.

How to save files programmatically

C++ Python
qSlicerCoreIOManager* coreIOManager = qSlicerCoreApplication::application()->coreIOManager();
qSlicerIO::IOProperties fileParameters;
fileParameters["nodeID"] = volumeNode->GetID();
fileParameters["fileName"] = "/path/of/file.ext";
coreIOManager->saveNodes("VolumeFile", parameters);

Works also with "ModelFile", "TransformFile", "SceneFile"...

>>> slicer.util.saveNode(volumeNode, '/path/of/file.ext')

Note that IO dialogs should be used as often as possible.

IO Dialogs

By default, all the IOs must be done through qSlicerDataDialog for reading files and qSlicerSaveDataDialog for writting files. However, due to historical reasons, it is possible to have custom dialogs for each node types.

It can be useful to create your own IO dialog to add custom behavior to the Slicer drag&drop default behavior.

How to register a dialog ?

C++ Python

Register dialogs in qSlicerXYZModule::setup():

qSlicerIOManager* ioManager = qSlicerApplication::application()->ioManager();
ioManager->registerDialog(new qSlicerXYZDialog(this));
ioManager->registerDialog(new qSlicerSaveXYZDialog(this));

Declare an XYZFileDialog in your XYZ.py module file. It will be automatically registered. For example:

class XYZFileDialog:
  def __init__(self, parent):
    self.parent = parent
    parent.fileType = 'XYZFile'
    parent.description = 'XYZ'
    parent.action = slicer.qSlicerFileDialog.Read
  def isMimeDataAccepted(self):
    accept = self.parent.mimeData().hasFormat("text/uri-list")
    self.parent.acceptMimeData(accept)
  def dropEvent(self):
    self.parent.dropEvent().accept()
  def execDialog(self):
    print 'exec' 

How to open a registered a dialog ?

C++ Python
qSlicerIOManager* ioManager = qSlicerApplication::application()->ioManager();
ioManager->openDialog("VolumeFile", qSlicerFileDialog::Read, qSlicerIO::IOProperties());
 io = slicer.app.ioManager()
 params = {}
 io.openDialog("VolumeFile", slicer.qSlicerFileDialog.Read, params)

And the special case for standard data file types:

slicer.util.openAddVolumeDialog()

How to order the list of drag&drop dialogs ?

By controlling the order of module initialization. This is done by adding module dependencies. Make your module dependent of of the "Data" would make it initialize after the Data module that registers the "Any Data" dialogs:

C++ Python
QStringList qSlicerXYZModule::dependencies() const
{
  QStringList moduleDependencies;
  moduleDependencies << "Data";
  return moduleDependencies;
}
def __init__(self, parent):
  ...
  parent.dependencies = ["Data"] class XYZFileDialog:
  ...