4. 数据的输入
在Nipype中提供了丰富的文件操作功能,使得我们在数据处理时不需要再把数据“复制过来,粘贴过去”,不仅极大地简化了操作,也避免了存储空间的浪费。
4.1 DataGrabber
顾名思义,DataGrabber是一个数据抓取工具,能够从指定的目录中按照一定的规则挑选出需要的数据。
假设我们使用的是DS00014的数据集合,其数据结构如下图所示:
对于这样一个数据集合,如何从中挑选出fingerfootlips任务的数据呢?其实可以这样:
import nipype.interfaces.io as nio
# 生成一个DataGrabber对象
ds = nio.DataGrabber()
# 指定根目录
ds.inputs.base_directory = '/data/ds000114'
# 指定模板,用来过滤文档
ds.inputs.template = 's*/ses-test/func/*fingerfootlips*.nii.gz'
# 需要注意的是,此处并没有对文档进行排序,如果需要排序需要指定sorted参数
ds.inputs.sort_filelist = True
# 执行
results = ds.run()
# 查看执行结果
results.outputs
使用这段代码,可以在/data/ds000114
文件夹下挑选出所有满足s*/ses-test/func/*fingerfootlips*.nii.gz
这一模式的文档。如果想要过滤出特定ID的被试怎么办呢?可以这样:
# 在生成DataGrabber对象的时候,需要指定infields参数
ds = nio.DataGrabber(infields=['subject_id'])
ds.inputs.base_directory = '/data/ds000114'
# 使用%02d来设计模板
ds.inputs.template = 'sub-%02d/ses-test/func/*fingerfootlips*.nii.gz'
ds.inputs.sort_filelist = True
# 指定subject_id的具体数值
ds.inputs.subject_id = [1, 7]
results = ds.run()
results.outputs
可见,Nipype的数据挑选方式有多么灵活。
官方文档中还给了几个其他的例子,其实并不是很好懂,建议没有时间的小伙伴们不要把太多的精力放在上面,因为接下来要介绍的SelectFiles会比DataGrabber更加的灵活和清晰。唯一还需要说明的是,DataGrabber的构造函数中,还有一个常用的参数,叫outfields
,这个参数的含义是DataGrabber的输出会变成一个类似于字典的数据结构,该结构的KEY是由outfileds
来确定。使用outfileds
后,就可以用同一个DataGrabber使用不同的模板来挑选各种各样的文件了。
4.2 SelectFiles
相比于DataGrabber,SelectFiles应该是一种更加直观的数据抓取工具,它使用的是基于大括号{}
的模板。这对于写过Rails和Flask的小伙伴来说应该会更加容易理解。先举个例子(这也是官方文档的例子):
msg = "This workflow uses {package}."
print(msg.format(package="FSL"))
这样一段Python代码执行之后,会看到什么结果呢?运行之后,我们可以看到:
This workflow uses FSL.
很直观的,源代码中的{package}变成了FSL,这也就是我所谓的基于大括号的模板,那么在实际中应该如何应用呢?再来看下一段代码:
from nipype import SelectFiles, Node
templates = {'anat': 'sub-{subject_id}\\ses-{ses_name}\\anat\\sub-{subject_id}_ses-{ses_name}_T1w.nii.gz',
'func': 'sub-{subject_id}\\ses-{ses_name}\\func\\sub-{subject_id}_ses-{ses_name}_task-{task_name}_bold.nii.gz'}
# Create SelectFiles node
sf = Node(SelectFiles(templates),
name='selectfiles')
# Location of the dataset folder
sf.inputs.base_directory = 'E:\\ds114_R2.0.0'
# Feed {}-based placeholder strings with values
sf.inputs.subject_id = '0[1,2]'
sf.inputs.ses_name = "test"
sf.inputs.task_name = 'fingerfootlips'
sf.run().outputs
运行上述代码之后,会得到如下结果:
anat = ['E:\\ds114_R2.0.0\\sub-01\\ses-test\\anat\\sub-01_ses-test_T1w.nii.gz', 'E:\\ds114_R2.0.0\\sub-02\\ses-test\\anat\\sub-02_ses-test_T1w.nii.gz']
func = ['E:\\ds114_R2.0.0\\sub-01\\ses-test\\func\\sub-01_ses-test_task-fingerfootlips_bold.nii.gz', 'E:\\ds114_R2.0.0\\sub-02\\ses-test\\func\\sub-02_ses-test_task-fingerfootlips_bold.nii.gz']
首先,可以看到,按照模板的设置,系统的输出会分成anat
和func
两部分,这个的效果就与DataGrabber的outfields
是一致的。其次模板中的{subject_id}
被替换成了01和02,{ses_name}
被替换成了test,{task_name}
被替换成了fingerfootlips。当然,和DataGrabber一样,模板中还是可以使用*
符号的。最后,在替换时,使用的是字符串直接替换,因此在给模板中的变量赋值的时候,是不能使用['01','02']这种形式的,否则系统会报错。
5. BIDS格式的输入
DataGrabber和SelectFiles为我们提供了便捷的数据选择工具,不过如果你使用的是标准的BIDS格式的数据(据说OpenfMRI上的数据都是以这种格式组织的),那么恭喜你,Nipype提供了更加便捷的数据访问方式。
5.1 pybids
要想享受Nipype和BIDS给你带来的种种便捷,还需要安装pybids这样一个python工具包,安装的方式很简单:
pip install pybids
我自己刚刚试了一下,想用conda安装,不过报错了,可能需要添加新的channel,回头再研究一下这个问题。用pip安装是没有任何问题的。
在使用pybids的时候,先导入BIDSLayout对象,在生成一个对象(好吧,其实你注意到了,我把数据换了路径存放,而且是在windows下)
from bids.grabbids import BIDSLayout
layout = BIDSLayout("E:\\Workspaces\\nipy\\data")
之后就可以使用layout来获得一些数据集的元信息以及一些具体的任务对应的数据文件。
先来看一下一些常用的元数据的获取方法,包括:
-
get_subjects()
获取所有的被试 get_modalities()
get_types()
-
get_tasks()
获取所有的任务 -
get_metadata
获取nii和nii.gz文件的元数据
如果要获得对应的数据文件,就可以直接调用get()
函数,并在get
函数中通过参数指明所需要的数据集的特征,常用的参数包括:
-
subject
指定被试,可以指定多个被试,例如:layout.get(subject=['01','02'])
就可以获取所有01和02的数据文件 modality
type
session
-
task
指定执行的任务,例如:layout.get(task='')
就可以获得 -
return_type
返回数据的类型。默认情况下,get
函数会返回一个文件对象,包括该文件所属的被试、session、task、type等信息,例如:File(filename='E:\\Workspaces\\nipy\\data\\sub-02\\ses-test\\func\\sub-02_ses-test_task-linebisection_events.tsv', subject='02', session='test', task='linebisection', type='events', modality='func')
,如果想要获得的只是文件路径,那么就需要指定该参数为'file'
-
extensions
指定返回文件的扩展文件名,例如extensions=['nii', 'nii.gz']
,就会返回所有后缀为nii和nii.gz的文件
5.2 pybids+nipype
pybids 已经很好的完成了所有想要做的工作,唯一的缺陷是还不能加入到我们的Workflow中,那么该如何实现呢?一个比较直观的想法就是将pybids的工作封装到Node中,而封装就需要先定义一个函数:
def get_niftis(subject_id, data_dir):
from bids.grabbids import BIDSLayout
layout = BIDSLayout(data_dir)
bolds = layout.get(subject=subject_id, type="bold", return_type='file', extensions=['nii', 'nii.gz'])
return bolds
之后,就需要定义自定义Node节点了
from nipype.pipeline import Node
from nipype.interfaces.utility import Function
BIDSDataGrabber = Node(Function(function=get_niftis, input_names=["subject_id", "data_dir"], output_names=["bolds"]),
name="BIDSDataGrabber")
之后,就是输入
BIDSDataGrabber.inputs.data_dir = "E:\\Workspaces\\nipy\\data"
BIDSDataGrabber.inputs.subject_id='01'
res = BIDSDataGrabber.run()
res.outputs
但是,这样是无法遍历所有的subjects,如果需要遍历,还需要指定iterables参数(此处没有想明白,需要在实际操作的时候再来完善,先把这部分的内容记录下来)
BIDSDataGrabber.iterables = ('subject_id', layout.get_subjects()[:2])
6. 数据的输出
在Nipype中,输出也是以模块的形式呈现的,主要包括以下几种输出模块:
- DataSink
- JSONFileSink
- MySQLSink
- SQLiteSink
- XNATSink
从名称上不难发现,其中绝大多数是和特定的数据工具相关,比如MySQLSink和SQLiteSink一定是与MySQL和SQLite两种数据库相关,XNATSink肯定是和XNAT数据相关,JSONFileSink应该是以JSON文件的格式来存储和管理数据,DataSink应该是一种更为通用的数据格式。因此,不妨从DataSink来开始我们的学习。
XNAT( Extensible Neuroimaging Archive Toolkit )是一个开源的软件平台,旨在帮助神经影像及其相关数据的管理和挖掘。
6.1 DataSink
如前所述,DataSink是一种较为通用的数据存储模块,通常用于存储结构化的输出。在Workflow中,其工作目录就如同一个缓存,会存储着各个阶段的计算结果(如日志信息等),如果直接浏览Workflow的工作目录会让人十分崩溃,而我们的目标通常是这些中间过程中的一小部分。DataSink可以将缓存中的结果提取出来并放置到不同的位置。
DataSink中第一个重要参数是base_directory
,该参数指定了DataSink存储的根目录。其他输入参数用于和其他模块对接,有趣的是DataSink用这些输入参数的名称指定了目录结构。其基本形式如下
string[[.[@]]string[[.[@]]string]] …
其中[]表示其中的内容是可选项,在DataSink中用.
表示目录结构关系,例如:contrasts.con
表示在根目录下会创建contrasts/con
这样的目录结构,如果带有@
则表示不会建立新的目录,例如contrasts.@con
,则会创建contrasts
目录,并把结果存储在contrasts
目录下。
如果觉得说的不清楚,还是来看个例子吧,先来看一下Workflow图:
代码如下:
datasink = pe.Node(nio.DataSink(), name='sinker')
datasink.inputs.base_directory = '/path/to/output'
workflow.connect(inputnode, 'subject_id', datasink, 'container')
我们先创建了一个DataSink节点,然后将其根目录指定为/path/to/output
,然后将inputnode节点的输出subject_id
与datasink的container
相连接,因此就会在/path/to/output/container
中存储InputNode的subject_id
输出。
workflow.connect(realigner, 'realigned_files', datasink, 'motion')
workflow.connect(realigner, 'realignment_parameters', datasink, 'motion.@par')
执行上面语句后,就会把头动矫正后的数据文件存储到/path/to/output/container/motion
文件夹下,并将头动参数也存储到该文件夹下。
6.2 MySQLSink
MySQLSink用于将一些值直接存储到MySQL数据库中,示例代码如下:
sql = MySQLSink(input_names=['subject_id', 'some_measurement'])
sql.inputs.database_name = 'my_database'
sql.inputs.table_name = 'experiment_results'
sql.inputs.username = 'root'
sql.inputs.password = 'secret'
sql.inputs.subject_id = 's1'
sql.inputs.some_measurement = 11.4
sql.run()
这对于用过数据库的同学而言应该十分简单,就不做过多的说明了。
大致看了一下,其他几种模块都大同小异,就不仔细学习了,如果需要的话再去看文档吧,目前应该用不到。
未完待续