# Output CPU information:
!cat /proc/cpuinfo | grep 'vendor' | uniq
!cat /proc/cpuinfo | grep 'model name' | uniq# In code cells you press Shift-Enter to evaluate your code and directly move to the next cell if it is already displayed. 
# Or press Ctrl-Enter to run a command without direclty moving to the next cell.

# Use lmod to load any software tool with a specific version
import lmod
await lmod.load('fsl/6.0.7.4')
await lmod.list()import os
os.environ["FSLOUTPUTTYPE"]="NIFTI_GZ" # Default is NIFTIfrom nipype.interfaces.fsl.base import Info
print(Info.version())
print(Info.output_type())
# If the FSL version is changed using lmod above, the kernel of the notebook needs to be restarted!# Load afni and spm as well
await lmod.load('afni/22.3.06')
await lmod.load('spm12/r7771') 
await lmod.list()# Download 2 subjects of the Flanker Dataset
PATTERN = "sub-0[1-2]"
 
!datalad install https://github.com/OpenNeuroDatasets/ds000102.git
!cd ds000102 && datalad get $PATTERNfrom nipype import Node, Workflow, DataGrabber, DataSink
from nipype.interfaces.utility import IdentityInterface
from nipype.interfaces import fsl
from nilearn import plotting 
from IPython.display import Image
import os
from os.path import join as opj
import matplotlib.pyplot as plt
import numpy as np
import nibabel as nib 

# Create directory for all the outputs (if it doesn't exist yet)
! [ ! -d output ] && mkdir output# help() function to get a general explanation of the class as well as a list of possible (mandatory and optional) input and output parameters 
fsl.BET.help()# Create an instance of the fsl.BET object
skullstrip = fsl.BET()

# Set input (and output)
skullstrip.inputs.in_file = 'ds000102/sub-01/anat/sub-01_T1w.nii.gz' 

skullstrip.inputs.out_file = 'output/T1w_nipype_bet.nii.gz' # Interfaces by default spit out results to the local directory why relative paths work (outputs are not stored in temporary files like in Nodes/Workflow)# Execute the node and shows outputs 
res = skullstrip.run()
res.outputs# Gives you transparency to what's happening under the hood with one additional line
skullstrip.cmdline# Create FSL BET Node with fractional intensity threshold of 0.3 and create a binary mask image

# For reasons that will become clear in the Workflow section, it's important to pass filenames to Nodes as absolute paths.
input_file = opj(os.getcwd(), 'ds000102/sub-01/anat/sub-01_T1w.nii.gz') 
output_file = opj(os.getcwd(), 'output/T1w_nipype_bet.nii.gz')

# Create FSL BET Node with fractional intensity threshold of 0.3 and create a binary mask image
bet = Node(fsl.BET(), name='bet_node')

# Define inputs 
bet.inputs.frac = 0.3
bet.inputs.mask = True
bet.inputs.in_file = input_file
bet.inputs.out_file = output_file

# Run the node
res = bet.run()# Shows produced outputs 
res.outputs# Plot original input file
plotting.plot_anat(input_file, title='BET input', cut_coords=(10,10,10),
          display_mode='ortho', dim=-1, draw_cross=False, annotate=False);

# Plot skullstripped output file (out_file) through the outputs property
plotting.plot_anat(res.outputs.out_file, title='BET output', cut_coords=(10,10,10),
          display_mode='ortho', dim=-1, draw_cross=False, annotate=False);in_file = input_file # See node example

# Skullstrip process
skullstrip = Node(fsl.BET(in_file=in_file, mask=True), name="skullstrip")

# Smooth process
smooth = Node(fsl.IsotropicSmooth(in_file=in_file, fwhm=4), name="smooth")

# Mask process
mask = Node(fsl.ApplyMask(), name="mask")# Create a working directory for all workflows created during this workshop
! [ ! -d output/working_dir ] && mkdir output/working_dir

wf_work_dir = opj(os.getcwd(), 'output/working_dir') 

# Initiation of a workflow with specifying the working directory.
# This specification of base_dir is very important (and is why we needed to use absolute paths above for the input files) because otherwise all the outputs would be saved somewhere in the temporary files.
wf = Workflow(name="smoothflow", base_dir=wf_work_dir )# Option 1: connect the binary mask of the skullstripping process to the mask node
wf.connect(skullstrip, "mask_file", mask, "mask_file")

# Option 2: connect the output of the smoothing node to the input of the masking node
wf.connect([(smooth, mask, [("out_file", "in_file")])])# Explore the workflow visually
wf.write_graph("workflow_graph.dot")

Image(filename=opj(wf_work_dir,"smoothflow/workflow_graph.png"))# Certain graph types also allow you to further inspect the individual connections between the nodes
wf.write_graph(graph2use='flat')

Image(filename=opj(wf_work_dir,"smoothflow/graph_detailed.png"))  # Execute the workflow (running serially here)
wf.run()# Check the working directories of the workflow
!tree  output/working_dir/smoothflow/ -I '*js|*json|*html|*pklz|_report'# Helper function to plot 3D NIfTI images
def plot_slice(fname):

    # Load the image
    img = nib.load(fname)
    data = img.get_fdata()

    # Cut in the middle of the brain
    cut = int(data.shape[-1]/2) + 10

    # Plot the data
    plt.imshow(np.rot90(data[..., cut]), cmap="gray")
    plt.gca().set_axis_off()f = plt.figure(figsize=(12, 4))
for i, img in enumerate([input_file,
                         opj(wf_work_dir, "smoothflow/smooth/sub-01_T1w_smooth.nii.gz"),
                         opj(wf_work_dir, "smoothflow/skullstrip/sub-01_T1w_brain_mask.nii.gz"),
                         opj(wf_work_dir, "smoothflow/mask/sub-01_T1w_smooth_masked.nii.gz")]):
    f.add_subplot(1, 4, i + 1)
    plot_slice(img)# Input: Set the input file path
data_dir = opj(os.getcwd(), 'ds000102') #base_directory of the data

# Dynamic parameters
subj_list = ['01', '02'] #subject_id
run_list = [1, 2] #run_id

# Initialise workflow
wf_input = Workflow(name='data_input', base_dir=wf_work_dir) # base_dir: Set path where nipype will store stepwise results
wf_input.config["execution"]["crashfile_format"] = "txt"# Create DataGrabber node with input fields for all dynamic parameters (e.g. subject identifier, run identifier), 
# as well as the two desired output fields anat and func.

dg = Node(
    interface= DataGrabber(infields=["subject_id","run_id"], outfields=["anat", "func"]),
    name="dg")

# Location of dataset folder
dg.inputs.base_directory = data_dir
# Necessary default parameters
dg.inputs.sort_filelist = True #return a sorted filelist to ensure to match files to correct runs/tasks
dg.inputs.template = "*" #wildcard
# Specify run_ids
dg.inputs.run_id = run_list# Define arguments to fill the wildcards in the below paths 
dg.inputs.template_args = dict(
    anat=[["subject_id","subject_id"]],
    func=[["subject_id","subject_id","run_id"]],
)

# Specify the template structure to find the specific data
dg.inputs.field_template = dict(
    anat="sub-%s/anat/sub-%s_T1w.nii.gz",
    func="sub-%s/func/sub-%s_task-flanker_run-%d_bold.nii.gz",
)infosource = Node(IdentityInterface(fields=["subject_id"]),
                  name="infosource")
# Run a workflow iterating over various inputs using the iterables attribute of nodes --> splits up the workflow
infosource.iterables = [("subject_id", subj_list)]# Connect the nodes and run the workflow
wf_input.connect([(infosource, dg, [("subject_id", "subject_id")])])

wf_input.run()# Initialise Workflow
wf_preproc = Workflow(name='preproc', base_dir=wf_work_dir)
wf_preproc.config["execution"]["crashfile_format"] = "txt"

# DataGrabber Node
dg = Node(
    interface= DataGrabber(infields=["subject_id", "run_id"], outfields=["func"]),
    name="dg")

# Location of dataset folder
dg.inputs.base_directory = data_dir
# Necessary default parameters
dg.inputs.sort_filelist = True #return a sorted filelist to ensure to match files to correct runs/tasks
dg.inputs.template = "*"
dg.inputs.run_id = 1
dg.inputs.subject_id = '01'
dg.inputs.template_args = dict(
    func=[["subject_id", "subject_id", "run_id"]],
)

# Specify the template structure to find the specific data
dg.inputs.field_template = dict(
    func="sub-%s/func/sub-%s_task-flanker_run-%d_bold.nii.gz",
)# Create Motion Correction Node
mcflirt = Node(fsl.MCFLIRT(save_plots=True), 
               name='mcflirt')

# Create Smoothing node
smooth = Node(fsl.IsotropicSmooth(fwhm=4),
              name='smooth')

# Connect the three nodes to each other
wf_preproc.connect([(dg, mcflirt, [("func", "in_file")]),
                    (mcflirt, smooth, [("out_file", "in_file")])])# Create DataSink object
sinker = Node(DataSink(), name='sinker')

# Name of the output folder
sinker.inputs.base_directory = opj(wf_work_dir, 'preproc/results')# Save output in one folder called 'preproc' with .@
wf_preproc.connect([(smooth, sinker, [('out_file', 'sub-01.@in_file')]),
                    (mcflirt, sinker, [('out_file', 'sub-01.@mc_img'),
                               ('par_file', 'sub-01.@par_file')]),
                   ])

# Visualize the graph
wf_preproc.write_graph(graph2use='colored', format='png', simple_form=True)
Image(filename=opj(wf_preproc.base_dir, wf_preproc.name, 'graph.png'))# Run workflow with distributed processing
wf_preproc.run('MultiProc')# Define substitution strings
substitutions = [('_bold', ''),
                 ('.nii.gz.par', '.par')]

# Feed the substitution strings to the DataSink node
sinker.inputs.substitutions = substitutions

# Run the workflow again with the substitutions in place
wf_preproc.run()%load_ext watermark

%watermark
%watermark --iversions