class_docstr: Add SPM specific options to SpecifyModel
  
  Adds:
  
     - concatenate_runs
     - output_units
  
  Examples
  --------
  >>> from nipype.algorithms import modelgen
  >>> from nipype.interfaces.base import Bunch
  >>> s = modelgen.SpecifySPMModel()
  >>> s.inputs.input_units = 'secs'
  >>> s.inputs.output_units = 'scans'
  >>> s.inputs.high_pass_filter_cutoff = 128.
  >>> s.inputs.functional_runs = ['functional2.nii', 'functional3.nii']
  >>> s.inputs.time_repetition = 6
  >>> s.inputs.concatenate_runs = True
  >>> evs_run2 = Bunch(conditions=['cond1'], onsets=[[2, 50, 100, 180]], durations=[[1]])
  >>> evs_run3 = Bunch(conditions=['cond1'], onsets=[[30, 40, 100, 150]], durations=[[1]])
  >>> s.inputs.subject_info = [evs_run2, evs_run3]
class_docstr_tok: ["Add","SPM","specific","options","to","SpecifyModel","Adds",":","-","concatenate_runs","-","output_units","Examples","--","--","--","--",">",">",">","from","nipype.algorithms","import","modelgen",">",">",">","from","nipype.interfaces.base","import","Bunch",">",">",">","s","=","modelgen.SpecifySPMModel","(",")",">",">",">","s.inputs.input_units","=","'secs'",">",">",">","s.inputs.output_units","=","'scans'",">",">",">","s.inputs.high_pass_filter_cutoff","=","128",".",">",">",">","s.inputs.functional_runs","=","[","'functional2.nii","'",",","'functional3.nii","'","]",">",">",">","s.inputs.time_repetition","=","6",">",">",">","s.inputs.concatenate_runs","=","True",">",">",">","evs_run2","=","Bunch","(","conditions=","[","'cond1","'","]",",","onsets=","[","[","2",",","50",",","100",",","180","]","]",",","durations=","[","[","1","]","]",")",">",">",">","evs_run3","=","Bunch","(","conditions=","[","'cond1","'","]",",","onsets=","[","[","30",",","40",",","100",",","150","]","]",",","durations=","[","[","1","]","]",")",">",">",">","s.inputs.subject_info","=","[","evs_run2",",","evs_run3","]"]
code: def _concatenate_info(self, infolist):
      nscans = []
      for i, f in enumerate(self.inputs.functional_runs):
          if isinstance(f, list):
              numscans = len(f)
          elif isinstance(f, (str, bytes)):
              img = load(f)
              numscans = img.shape[3]
          else:
              raise Exception(
                  "Functional input not specified correctly"
              )
          nscans.insert(i, numscans)
  
      # now combine all fields into 1
      # names, onsets, durations, amplitudes, pmod, tmod, regressor_names,
      # regressors
      infoout = infolist[0]
      for j, val in enumerate(infolist[0].durations):
          if len(infolist[0].onsets[j]) > 1 and len(val) == 1:
              infoout.durations[j] = infolist[0].durations[j] * len(
                  infolist[0].onsets[j]
              )
  
      for i, info in enumerate(infolist[1:]):
          # info.[conditions, tmod] remain the same
          if info.onsets:
              for j, val in enumerate(info.onsets):
                  if self.inputs.input_units == "secs":
                      onsets = np.array(
                          info.onsets[j]
                      ) + self.inputs.time_repetition * sum(
                          nscans[0 : (i + 1)]
                      )
                      infoout.onsets[j].extend(onsets.tolist())
                  else:
                      onsets = np.array(info.onsets[j]) + sum(
                          nscans[0 : (i + 1)]
                      )
                      infoout.onsets[j].extend(onsets.tolist())
  
              for j, val in enumerate(info.durations):
                  if len(info.onsets[j]) > 1 and len(val) == 1:
                      infoout.durations[j].extend(
                          info.durations[j] * len(info.onsets[j])
                      )
                  elif len(info.onsets[j]) == len(val):
                      infoout.durations[j].extend(info.durations[j])
                  else:
                      raise ValueError(
                          f"Mismatch in number of onsets and durations for run {i + 2}, "
                          f"condition {j + 1}"
                      )
  
              if hasattr(info, "amplitudes") and info.amplitudes:
                  for j, val in enumerate(info.amplitudes):
                      infoout.amplitudes[j].extend(info.amplitudes[j])
  
              if hasattr(info, "pmod") and info.pmod:
                  for j, val in enumerate(info.pmod):
                      if val:
                          for key, data in enumerate(val.param):
                              infoout.pmod[j].param[key].extend(data)
  
          if hasattr(info, "regressors") and info.regressors:
              # assumes same ordering of regressors across different
              # runs and the same names for the regressors
              for j, v in enumerate(info.regressors):
                  infoout.regressors[j].extend(info.regressors[j])
  
          # insert session regressors
          if (
              not hasattr(infoout, "regressors")
              or not infoout.regressors
          ):
              infoout.regressors = []
          onelist = np.zeros((1, sum(nscans)))
          onelist[0, sum(nscans[0:i]) : sum(nscans[0 : (i + 1)])] = 1
          infoout.regressors.insert(
              len(infoout.regressors), onelist.tolist()[0]
          )
      return [infoout], nscans
  
imports: from copy import deepcopy
  import csv, math, os
  from nibabel import load
  import numpy
  from ..interfaces.base import BaseInterface, TraitedSpec, InputMultiPath, traits, File, Bunch, BaseInterfaceInputSpec, isdefined
  from ..utils.filemanip import ensure_list
  from ..utils.misc import normalize_mc_params
  from ..None import config, logging
label_desc: Use image node_id 1 for calling the SpecifySPMModel obj's underlying member method code with example usage: obj._concatenate_info(infolist) and returns: list,  nscans