Convert between MultiVolume and Volume Sequence?

Hello, I am interested in doing perfusion analysis on dynamic contrast enhanced MRI images using Slicer. I see that there are two older extensions which allow this, PkModeling and DSCMRIAnalysis, both of which are CLI modules and both of which expect MultiVolumes as input. Using a recent Slicer 5.1 preview, I still have the option to load these series as MultiVolumes using the Advanced options in the DICOM browser, but my issue is that I want to do a rigid registration step before running the analysis. Sequence Registration is an easy solution for this and works well as long as I provide inputs in a Volume Sequence rather than a MultiVolume. I know that MultiVolumes and Sequences are conceptually quite similar and I think that converting between the two should be pretty easy, but I haven’t found an existing method for this. Can anyone point me in the right direction?

I also considered trying to update the DSCMRIAnalysis extension to take Volume Sequences as input instead of MultiVolumes, but the extension is a CLI module and I think might require getting into building Slicer and compiling from source and I think that will likely take more time and learning than I have available. A more feasible path for me seems to be to figure out how to convert a Sequence to a MultiVolume and then feed the existing CLI extension the input it expects.

My workflow would then be to load the perfusion images as a Volume Sequence, register using Sequence Registration, convert to MultiVolume, and run DSCMRIAnalysis.

Found a partial answer here: Sequences — 3D Slicer documentation. The MultiVolume to Sequence conversion can be accomplished by saving the MultiVolume to .nrrd and then loading the nrrd as a sequence.

The reverse does not seem to be possible in the same way. If we save a Sequence to nrrd, it is saved as a .seq.nrrd, and the loading options do not seem to include MultiVolume. The MultiVolume Importer module only accepts a folder as an input, so it will not allow you to select a .seq.nrrd or a .nrrd file. However, I think it should work to write a small python script to save each volume in a Sequence to a temporary folder and then use the MultiVolume Importer to load it as a MultiVolume. I’m going to try this now and see if it works, and I’ll post with the result.

That sounds like a great idea.

1 Like

OK, for the basic Sequence → MultiVolume conversion, saving individual volumes to a temporary folder and then using MultiVolume Importer works OK. However, for DSCMRIAnalysis to work properly, the MultiVolume needs to have some of the DICOM tags stored as attributes, and these are not collected in the loading as Sequence step. However, I can restore these by copying attribute values from the original MultiVolume (from before registration) to the registered MultiVolume. The attributes in the original are

>> mvOrig.GetAttributeNames()
('DICOM.instanceUIDs', 'MultiVolume.DICOM.EchoTime', 'MultiVolume.DICOM.FlipAngle', 'MultiVolume.DICOM.RepetitionTime', 'MultiVolume.FrameIdentifyingDICOMTagName', 'MultiVolume.FrameIdentifyingDICOMTagUnits', 'MultiVolume.FrameLabels', 'MultiVolume.NumberOfFrames', 'MultiVolume.ParseStrategy')

I think the key ones needed are the echo time, flip angle, and repetition time. Possibly also the frame labels (these are based on the repetition time in the original). It seems safe just to copy all of them over.

Final functional workflow goes as follows:

  1. Using DICOM browser, import perfusion series as a MultiVolume and as a VolumeSequence.
  2. Use Sequence Registration with the “rigid (all)” preset to register the sequence to a new Volume Sequence.
  3. Save the individual nodes in the registered volume sequence to individual .nrrd files in a temporary folder.
  4. Open MultiVolumeImporter and point it at the temporary folder and load as MultiVolume
  5. Copy the attribute values from the original MultiVolume (non-registered) to the new MultiVolume (registered).
  6. Run DSCMRIAnalysis on the registered MultiVolume.

Python for step 3:

import os
import os.path
outDir = os.path.join(slicer.app.temporaryPath, 'MyUniqSubDirectoryForOutput')
os.mkdir(outDir)
seqNode = getNode('vtkMRMLSequenceNode1') # use MRML ID of the loaded sequence
browserNode = slicer.modules.sequences.logic().GetFirstBrowserNodeForSequenceNode(seqNode)
for volNum in range(browserNode.GetNumberOfItems()):
    browserNode.SetSelectedItemNumber(volNum)
    volNode = browserNode.GetProxyNode(seqNode)
    saveName = 'Im%04d.nrrd' % volNum
    slicer.util.saveNode(volNode, os.path.join(outDir, saveName)

I found that you needed to use the browser node in the above code because slicer.util.saveNode(seqNode.GetNthDataNode(volNum), saveName) does not work because the internal data nodes of the sequence are not technically present in the MRML scene, only the current proxy node is, and therefore saveNode can’t find them to save and throws an error.

Python for step 5:

mvOrig = getNode('vtkMRMLMultiVolumeNode1') # use MRML ID for original MultiVolumeNode
mvReg = getNode('vtkMRMLMultiVolumeNode2') # use MRML ID for imported registered 
for attr in mvOrig.GetAttributeNames():
    mvReg.SetAttribute(attr, mvOrig.GetAttribute(attr))
1 Like

We need to move away from investing any time into MultiVolume infrastructure and instead spend that time with making everything compatible with Volume Sequences (so that we can phase out the limited and mostly redundant MultiVolumes). To facilitate this, I’ve submitted a pull request that allows selecting a volume sequence (a sequence node that contains scalar volumes) as CLI module input. After the pull request is merged, volume sequence can be used everywhere instead of multivolume as input in all CLI modules.

@mikebind it would be great if you could test this.

1 Like

Thanks @lassoan! I will test this as soon as it is available (tomorrow’s preview build?).

Thinking ahead, I am guessing I will need to assign attributes to the Sequence with names “MultiVolume.DICOM.EchoTime”, etc., filled in based on DICOM metadata tag values, because that is what the DSCMRIAnalysis module looks for. Would you anticipate that working OK using SetAttribute() on the sequence node, or are there other barriers you foresee?

All the attributes are supposed to be collected from DICOM and saved into the nrrd file the same way as it is done for MultiVolumes.

1 Like

When I load the perfusion sequence as a volume sequence, the sequence node has no attributes (seqNode.GetAttributeNames() produces an empty list), and the proxy node has only attributes named 'DICOM.instanceUIDs' and 'Sequences.BaseName'. Am I looking in the wrong place? I think DSCMRIAnalysis is looking for the echo time, flip angle, and series of repetition times in attributes on the base MultiVolume named 'MultiVolume.DICOM.EchoTime' (DSC_Analysis/DSCMRIAnalysis.cxx at 21778ff278201a113b58f4bb4023e412a1e37b58 · QIICR/DSC_Analysis · GitHub), 'MultiVolume.DICOM.FlipAngle', and then a series of time stamps in 'MultiVolume.FrameLabels' which are spaced by the repetition time.

I think I can gather the needed metadata using the DICOM database and the instance UIDs, but I think I will need to use that metadata to assign the expected attributes on the sequence node before DSCMRIAnalysis will work as it is currently written.

I have verified that I can get the needed metadata from the DICOM instance UIDs as follows:

# Get proxy node for desired sequence node 
browserNode = slicer.modules.sequences.logic().GetFirstBrowserNodeForSequenceNode(seqNode)
proxNode = browserNode.GetProxyNode(seqNode)
# Get sop instance UIDs list from sequence proxy node attribute 
instUIDs = proxNode.GetAttribute('DICOM.instanceUIDs').split()
# Get first DICOM file name and location from Slicer's DICOM database
dcmFileName = slicer.dicomDatabase.fileForInstance(instUIDs[0])
# Read header metadata into pydicom data set
import pydicom
ds = pydicom.dcmread(dcmFileName)
# Gather needed parameters
echoTime = ds.EchoTime
flipAngle = ds.FlipAngle
repetitionTime = ds.RepetitionTime

I have now verified that the modifications that @lassoan made to allow Sequence input to DSCMRIAnalysis were successful. As suspected, it is required to add some particular expected attribute tags to the Sequence before DSCMRIAnalysis will run correctly.

# First gather the echoTime, flipAngle, and repetitionTime as shown in the previous reply message
# Make list of needed frame labels
frameLabels = " ".join(["%i"%(idx*repetitionTime) for idx in range(seqNode.GetNumberOfDataNodes())])
# Add attributes to Sequence node (note that conversion is necessary from pydicom output to regular strings)
seqNode.SetAttribute('MultiVolume.DICOM.EchoTime', "%0.1f" % echoTime)
seqNode.SetAttribute('MultiVolume.DICOM.FlipAngle', "%0.1f" % flipAngle)
seqNode.SetAttribute('MultiVolume.FrameLabels', frameLabels)

Once these attributes are set, DSCMRIAnalysis module runs on this Sequence without error!

Thanks for the update, it is very promising! However, it would be even nicer if this manual adding of the acquisition information was not necessary.

It seems that MultiVolumeImporter DICOM plugin can write these tags into node attributes of the imported sequence. Could you check why these node attributes not end up being added?

Yes, I will look into this. I didn’t realize these attributes should be automatically added to all sequences on import from DICOM. That would indeed be more convenient.

1 Like

The problem is in the load() function: MultiVolumeImporter/MultiVolumeImporterPlugin.py at db805948534563566f0a39c63e8d60fac0b63dcd · fedorov/MultiVolumeImporter · GitHub

The attributes come in loadable.multivolume for both sequences and multivolumes, but where the output multivolume is just this input multivolume (with attributes intact), the output sequence is created from scratch and never has the attributes added to it. I’ll fix this and send a PR to the MultiVolumeImporter repo.

That would be great, thank you!

PR sent: ENH: Transfer attributes to Sequence node by mikebind · Pull Request #51 · fedorov/MultiVolumeImporter · GitHub

2 Likes

PR merged - thank you!

1 Like