def __init__(self, *args, **kwargs): """ Constructor. Can take same arguments as the fsl.data.image.Image constructor but also supports keyword ``param_names`` argument. This should be a list of parameter names which matches the size of the MVN """ self.param_names = kwargs.get("param_names", None) Image.__init__(self, *args, **kwargs) self.nparams = self._get_num_params() if self.param_names is None: self.param_names = ["Parameter %i" % idx for idx in range(self.nparams)] elif len(self.param_names) != self.nparams: raise ValueError("MVN contains %i parameters, but %i parameter names were provided")
def __init__(self, image, name=None, **kwargs): if image is None: raise ValueError( "No image data (filename, Nibabel object or Numpy array)") # This is sort-of a bug in nibabel or fslpy - it passes the kwargs to the # nibabel.load function which does not expect extra keyword arguments. So we will extract # those that fslpy/nibabel might expect and only pass these img_kwargs = ("header", "xform", "loadData", "calcRange", "indexed", "threaded", "dataSource") img_args = dict([(k, v) for k, v in kwargs.items() if k in img_kwargs]) if kwargs.pop("calib_first_vol", False): # First volume is an M0 calibration image - stip it off and initialize the image with the # remaining data temp_img = Image(image, name="temp", **img_args) img_args.pop("header", None) self.calib = Image(temp_img.data[..., 0], name="calib", header=temp_img.header, **img_args) Image.__init__(self, temp_img.data[..., 1:], name=name, header=temp_img.header, **img_args) else: self.calib = None Image.__init__(self, image, name=name, **img_args) order = kwargs.pop("order", None) iaf = kwargs.pop("iaf", None) ibf = kwargs.pop("ibf", None) ntis = kwargs.pop("ntis", None) nplds = kwargs.pop("nplds", None) tis = kwargs.pop("tis", None) tes = kwargs.pop("tes", None) plds = kwargs.pop("plds", None) rpts = kwargs.pop("rpts", kwargs.pop("nrpts", None)) phases = kwargs.pop("phases", None) nphases = kwargs.pop("nphases", None) nenc = kwargs.pop("nenc", kwargs.pop("ntc", None)) if self.ndim not in (3, 4): raise ValueError("3D or 4D data expected") # Check for multi-TE data # # Note that for the moment we assume that multi-TEs are applied at every TI/PLD. # Variable repeats/taus are only per-TI. Default is one TE with value 0 # # Sets the attributes tes, ntes if tes is not None: if isinstance(tes, six.string_types): tes = [float(te) for te in tes.split(",")] ntes = len(tes) self.setMeta("tes", tes) self.setMeta("ntes", len(tes)) else: self.setMeta("tes", [ 0, ]) self.setMeta("ntes", 1) # Determine the data ordering # # Sets the metadata: iaf (str), order (str) iaf, order, ibf_guessed = data_order(iaf, ibf, order, self.ntes > 1) self.setMeta("order", order) self.setMeta("iaf", iaf.lower()) # Determine the number of labelling images present. This may be label/control # pairs, a set of multiple phases, vessel encoding cycles or already differenced data # # Sets the metadata: ntc (int), phases (list or None) if self.iaf in ("tc", "ct"): ntc = 2 elif self.iaf == "mp": if phases is None and nphases is None: raise ValueError( "Multiphase data specified but number of phases not given") elif phases is not None: if nphases is not None and nphases != len(phases): raise ValueError( "Number of phases is not consistent with length of phases list" ) else: phases = [pidx * 360 / nphases for pidx in range(nphases)] if isinstance(phases, six.string_types): phases = [float(ph) for ph in phases.split(",")] phases = phases ntc = len(phases) elif self.iaf in ("ve", "vediff"): self.setMeta("nenc", nenc) if nenc is None: raise ValueError( "Vessel encoded data specified but number of encoding cycles not given" ) elif self.iaf == "ve": ntc = nenc elif nenc % 2 == 0: ntc = int(nenc / 2) else: raise ValueError( "Subtracted vessel encoded data must have had an even number of encoding cycles" ) else: ntc = 1 self.setMeta("ntc", ntc) self.setMeta("phases", phases) # Determine the number and type of delay images present. These may be given as TIs or PLDs. # # Internally we always have TIs, and only have PLDs as well if they were provided. If PLDs # were provided, the TIs are derived by adding on the bolus duration # # Sets the attributes tis (list), ntis (int), have_plds (bool), plds (list) if tis is not None and plds is not None: raise ValueError("Cannot specify PLDs and TIs at the same time") have_plds = False if nplds is not None: ntis = nplds have_plds = True if plds is not None: tis = plds have_plds = True if ntis is None and tis is None: raise ValueError("Number of TIs/PLDs not specified") elif tis is not None: if isinstance(tis, six.string_types): tis = [float(ti) for ti in tis.split(",")] ntis = len(tis) if ntis is not None and len(tis) != ntis: raise ValueError( "Number of TIs/PLDs specified as: %i, but a list of %i TIs/PLDs was given" % (ntis, len(tis))) self.setMeta("ntis", int(ntis)) self.setMeta("have_plds", have_plds) if have_plds: self.setMeta("plds", tis) else: self.setMeta("tis", tis) if ibf_guessed and len(self.tis) > 1: warnings.warn( "Data order was not specified for multi-TI data - assuming blocks of repeats" ) # Determine the number of repeats (fixed or variable) # # Sets the attribute rpts (list, one per TI/PLD) nvols_norpts = self.ntc * self.ntis * self.ntes if rpts is None: # Calculate fixed number of repeats if self.nvols % nvols_norpts != 0: raise ValueError( "Data contains %i volumes, inconsistent with %i TIs and %i labelling images at %i TEs" % (self.nvols, self.ntis, self.ntc, self.ntes)) rpts = [int(self.nvols / nvols_norpts)] * self.ntis else: if isinstance(rpts, six.string_types): rpts = [int(rpt) for rpt in rpts.split(",")] elif isinstance(rpts, (int, np.integer)): rpts = [ rpts, ] if len(rpts) == 1: rpts *= self.ntis elif len(rpts) != self.ntis: raise ValueError( "%i TIs specified, inconsistent with %i variable repeats" % (self.ntis, len(rpts))) if sum(rpts) * self.ntc * self.ntes != self.nvols: raise ValueError( "Data contains %i volumes, inconsistent with %i labelling images at %i TEs and total of %i repeats" % (self.nvols, self.ntc, self.ntes, sum(rpts))) self.setMeta("rpts", rpts) # Bolus durations should be a sequence same length as TIs/PLDs # # Sets the attributes taus (list, one per TI/PLD) taus = kwargs.pop("bolus", kwargs.pop("taus", kwargs.pop("tau", None))) if taus is None: taus = 1.8 if isinstance(taus, six.string_types): taus = [float(tau) for tau in taus.split(",")] elif isinstance(taus, (float, int, np.integer)): taus = [ float(taus), ] * self.ntis if len(taus) == 1: taus = taus * ntis if len(taus) != self.ntis: raise ValueError( "%i bolus durations specified, inconsistent with %i TIs/PLDs" % (len(taus), self.ntis)) self.setMeta("taus", taus) # Labelling type. CASL/pCASL normally associated with PLDs but can pass TIs instead. # However we would not expect PLDs for a non-CASL aquisition so this generates a warning # # If PLDs were provided, TIs are derived by adding the bolus duration to the PLDs # # Sets the attributes casl (bool), updates tis (list) casl = kwargs.pop("casl", None) if casl is None: casl = self.have_plds if self.have_plds: if not casl: warnings.warn( "PLDs specified but aquisition was not CASL/pCASL - will treat these as TIs" ) self.setMeta("casl", casl) # Other acquisition parameters self.setMeta("slicedt", kwargs.pop("slicedt", 0)) self.setMeta("sliceband", kwargs.pop("sliceband", None)) self.setMeta("artsuff", kwargs.pop("artsupp", False))