{"action":"created","comment":{"url":"https://api.github.com/repos/nistats/nistats/pulls/comments/66412943","id":66412943,"diff_hunk":"@@ -0,0 +1,561 @@\n+# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-\n+# vi: set ft=python sts=4 ts=4 sw=4 et:\n+\"\"\"\n+This module presents an interface to use the glm implemented in\n+nistats.regression.\n+\n+It contains the GLM and contrast classes that are meant to be the main objects\n+of fMRI data analyses.\n+\"\"\"\n+\n+from warnings import warn\n+import time\n+import sys\n+import os\n+\n+import numpy as np\n+import glob\n+from nibabel import Nifti1Image\n+\n+from sklearn.base import BaseEstimator, TransformerMixin, clone\n+from sklearn.externals.joblib import Memory\n+from nilearn._utils.niimg_conversions import check_niimg\n+from nilearn._utils import CacheMixin\n+from nilearn._utils.class_inspect import get_params\n+from nilearn.input_data import NiftiMasker\n+from sklearn.externals.joblib import Parallel, delayed\n+\n+from .regression import OLSModel, ARModel, SimpleRegressionResults\n+from .design_matrix import make_design_matrix\n+from .contrasts import _summary_contrast\n+from .utils import _basestring, _check_run_tables\n+\n+\n+def mean_scaling(Y, axis=0):\n+    \"\"\"Scaling of the data to have percent of baseline change along the\n+    specified axis\n+\n+    Parameters\n+    ----------\n+    Y : array of shape (n_time_points, n_voxels)\n+       The input data.\n+\n+    Returns\n+    -------\n+    Y : array of shape (n_time_points, n_voxels),\n+       The data after mean-scaling, de-meaning and multiplication by 100.\n+\n+    mean : array of shape (n_voxels,)\n+        The data mean.\n+    \"\"\"\n+    mean = Y.mean(axis=axis)\n+    if (mean == 0).any():\n+        warn('Mean values of 0 observed.'\n+             'The data have probably been centered.'\n+             'Scaling might not work as expected')\n+    mean = np.maximum(mean, 1)\n+    Y = 100 * (Y / mean - 1)\n+    return Y, mean\n+\n+\n+def _ar_model_fit(X, val, Y):\n+    return ARModel(X, val).fit(Y)\n+\n+\n+def run_glm(Y, X, noise_model='ar1', bins=100, n_jobs=1, verbose=0):\n+    \"\"\" GLM fit for an fMRI data matrix\n+\n+    Parameters\n+    ----------\n+    Y : array of shape (n_time_points, n_voxels)\n+        The fMRI data.\n+\n+    X : array of shape (n_time_points, n_regressors)\n+        The design matrix.\n+\n+    noise_model : {'ar1', 'ols'}, optional\n+        The temporal variance model. Defaults to 'ar1'.\n+\n+    bins : int, optional\n+        Maximum number of discrete bins for the AR(1) coef histogram.\n+\n+    n_jobs : int, optional\n+        The number of CPUs to use to do the computation. -1 means\n+        'all CPUs'.\n+\n+    verbose : int, optional\n+        The verbosity level. Defaut is 0\n+\n+    Returns\n+    -------\n+    labels : array of shape (n_voxels,),\n+        A map of values on voxels used to identify the corresponding model.\n+\n+    results : dict,\n+        Keys correspond to the different labels values\n+        values are RegressionResults instances corresponding to the voxels.\n+    \"\"\"\n+    acceptable_noise_models = ['ar1', 'ols']\n+    if noise_model not in acceptable_noise_models:\n+        raise ValueError(\n+            \"Acceptable noise models are {0}. You provided 'noise_model={1}'\".\\\n+                format(acceptable_noise_models, noise_model))\n+\n+    if Y.shape[0] != X.shape[0]:\n+        raise ValueError(\n+            'The number of rows of Y should match the number of rows of X.'\n+            ' You provided X with shape {0} and Y with shape {1}'.\\\n+                format(X.shape, Y.shape))\n+\n+    # Create the model\n+    ols_result = OLSModel(X).fit(Y)\n+\n+    if noise_model == 'ar1':\n+        # compute and discretize the AR1 coefs\n+        ar1 = ((ols_result.resid[1:] * ols_result.resid[:-1]).sum(axis=0) /\n+               (ols_result.resid ** 2).sum(axis=0))\n+        del ols_result\n+        ar1 = (ar1 * bins).astype(np.int) * 1. / bins\n+        # Fit the AR model acccording to current AR(1) estimates\n+        results = {}\n+        labels = ar1\n+        # Parallelize by creating a job per ARModel\n+        vals = np.unique(ar1)\n+        ar_result = Parallel(n_jobs=n_jobs, verbose=verbose)(\n+            delayed(_ar_model_fit)(X, val, Y[:, labels == val]) for val in vals)\n+        for val, result in zip(vals, ar_result):\n+            results[val] = result\n+        del vals\n+        del ar_result\n+\n+    else:\n+        labels = np.zeros(Y.shape[1])\n+        results = {0.0: ols_result}\n+\n+    return labels, results\n+\n+\n+class FirstLevelModel(BaseEstimator, TransformerMixin, CacheMixin):\n+    \"\"\" Implementation of the General Linear Model for single session fMRI data\n+\n+    Parameters\n+    ----------\n+\n+    t_r: float\n+        This parameter indicates repetition times of the experimental runs.\n+        It is necessary to correctly consider times in the design matrix.\n+        This parameter is also passed to nilearn.signal.clean.\n+        Please see the related documentation for details.\n+\n+    slice_time_ref: float, optional (default 0.)\n+        This parameter indicates the time of the reference slice used in the\n+        slice timing preprocessing step of the experimental runs. It is\n+        expressed as a percentage of the t_r (time repetition), so it can have\n+        values between 0. and 1.\n+\n+    hrf_model : string, optional\n+        This parameter specifies the hemodynamic response function (HRF) for\n+        the design matrices. It can be 'canonical', 'canonical with derivative'\n+        or 'fir'.\n+\n+    drift_model : string, optional\n+        This parameter specifies the desired drift model for the design\n+        matrices. It can be 'polynomial', 'cosine' or 'blank'.\n+\n+    period_cut : float, optional\n+        This parameter specifies the cut period of the low-pass filter in\n+        seconds for the design matrices.\n+\n+    drift_order : int, optional\n+        This parameter specifices the order of the drift model (in case it is\n+        polynomial) for the design matrices.\n+\n+    fir_delays : array of shape(n_onsets) or list, optional\n+        In case of FIR design, yields the array of delays used in the FIR\n+        model.\n+\n+    min_onset : float, optional\n+        This parameter specifies the minimal onset relative to the design\n+        (in seconds). Events that start before (slice_time_ref * t_r +\n+        min_onset) are not considered.\n+\n+    mask: Niimg-like, NiftiMasker or MultiNiftiMasker object, optional,\n+        Mask to be used on data. If an instance of masker is passed,\n+        then its mask will be used. If no mask is given,\n+        it will be computed automatically by a MultiNiftiMasker with default\n+        parameters.\n+\n+    target_affine: 3x3 or 4x4 matrix, optional\n+        This parameter is passed to nilearn.image.resample_img. Please see the\n+        related documentation for details.\n+\n+    target_shape: 3-tuple of integers, optional\n+        This parameter is passed to nilearn.image.resample_img. Please see the\n+        related documentation for details.\n+\n+    low_pass: False or float, optional\n+        This parameter is passed to nilearn.signal.clean.\n+        Please see the related documentation for details.\n+\n+    high_pass: False or float, optional\n+        This parameter is passed to nilearn.signal.clean.\n+        Please see the related documentation for details.\n+\n+    smoothing_fwhm: float, optional\n+        If smoothing_fwhm is not None, it gives the size in millimeters of the\n+        spatial smoothing to apply to the signal.\n+\n+    memory: string, optional\n+        Path to the directory used to cache the masking process and the glm\n+        fit. By default, no caching is done. Creates instance of joblib.Memory.\n+\n+    memory_level: integer, optional\n+        Rough estimator of the amount of memory used by caching. Higher value\n+        means more memory for caching.\n+\n+    standardize : boolean, optional\n+        If standardize is True, the time-series are centered and normed:\n+        their variance is put to 1 in the time dimension.\n+\n+    signal_scaling: bool, optional,\n+        If True, fMRI signals are scaled to the mean value of scaling_axis\n+        Incompatible with standardize (standardize=False is enforced when\\\n+        signal_scaling is True).\n+\n+    scaling_axis: int or tuple, optional,\n+        Axis used for signal scaling. 0 refers to mean scaling each voxel with\n+        respect to time, 1 refers to mean scaling each time point with respect\n+        to all voxels and (0, 1) refers to scaling with respect to voxels and\n+        time, which is known as grand mean scaling.\n+\n+    noise_model : {'ar1', 'ols'}, optional\n+        The temporal variance model. Defaults to 'ar1'\n+\n+    verbose : integer, optional\n+        Indicate the level of verbosity. By default, nothing is printed.\n+\n+    n_jobs : integer, optional\n+        The number of CPUs to use to do the computation. -1 means\n+        'all CPUs', -2 'all CPUs but one', and so on.\n+\n+    minimize_memory : boolean, optional\n+        Gets rid of some variables on the model fit results that are not\n+        necessary for contrast computation and would only be useful for\n+        further inspection of model details. This has an important impact\n+        on memory consumption. True by default.\n+\n+    subject_id : string, optional\n+        id of the subject of this first_level_model instance. Will be used\n+        to create subject specific memory cache if caching allowed.\n+\n+    Attributes\n+    ----------\n+    labels : array of shape (n_voxels,),\n+        a map of values on voxels used to identify the corresponding model\n+\n+    results : dict,\n+        with keys corresponding to the different labels values\n+        values are RegressionResults instances corresponding to the voxels\n+    \"\"\"\n+    def __init__(self, t_r=None, slice_time_ref=None, hrf_model='glover',\n+                 drift_model='cosine', period_cut=128, drift_order=1,\n+                 fir_delays=[0], min_onset=-24, mask=None, target_affine=None,\n+                 target_shape=None, low_pass=None, high_pass=None,\n+                 smoothing_fwhm=None, memory=None, memory_level=1,\n+                 standardize=False, signal_scaling=True, scaling_axis=0,\n+                 noise_model='ar1', verbose=1, n_jobs=1,\n+                 minimize_memory=True, model_id=None):\n+        # design matrix parameters\n+        self.t_r = t_r\n+        self.slice_time_ref = slice_time_ref\n+        self.hrf_model = hrf_model\n+        self.drift_model = drift_model\n+        self.period_cut = period_cut\n+        self.drift_order = drift_order\n+        self.fir_delays = fir_delays\n+        self.min_onset = min_onset\n+        # glm parameters\n+        self.mask = mask\n+        self.target_affine = target_affine\n+        self.target_shape = target_shape\n+        self.low_pass = low_pass\n+        self.high_pass = high_pass\n+        self.smoothing_fwhm = smoothing_fwhm\n+        if memory is not None:\n+            first_level_cache_path = os.path.join(memory, 'first_level_cache')\n+            if model_id is None:\n+                model_paths = glob.glob(os.path.join(first_level_cache_path,\n+                                        'model_*'))\n+                model_paths.sort()\n+                last_m = int(os.path.basename(model_paths[-1].split('_')[1]))\n+                model_id = 'model_%03d' % (last_m + 1)\n+            self.memory = Memory(os.path.join(first_level_cache_path,\n+                                              model_id))\n+        else:\n+            self.memory = Memory(None)\n+        self.memory_level = memory_level\n+        self.standardize = standardize\n+        self.signal_scaling = signal_scaling\n+        if self.signal_scaling:\n+            self.standardize = False\n+        self.scaling_axis = scaling_axis\n+        self.noise_model = noise_model\n+        self.verbose = verbose\n+        self.n_jobs = n_jobs\n+        self.minimize_memory = minimize_memory\n+        self.model_id = model_id\n+\n+    def get_model_parameters(self, ignore=None):\n+        \"\"\"Get model parameters as a dictionary.\n+\n+        Parameters\n+        ----------\n+        ignore: None or list of strings, optional\n+            Names of the parameters that are not returned.\n+\n+        Returns\n+        -------\n+        params: dict\n+            The dict of parameters\n+        \"\"\"\n+        return get_params(FirstLevelModel, self, ignore=ignore)\n+\n+    def fit(self, run_imgs, paradigms=None, confounds=None,\n+            design_matrices=None):\n+        \"\"\" Fit the GLM\n+\n+        For each run:\n+        1. create design matrix\n+        2. do a masker job: fMRI_data -> Y\n+        3. fit regression to (Y, X)\n+\n+        Parameters\n+        ----------\n+        run_imgs: Niimg-like object or list of Niimg-like objects,\n+            See http://nilearn.github.io/building_blocks/manipulating_mr_images.html#niimg.\n+            Data on which the GLM will be fitted. If this is a list,\n+            the affine is considered the same for all.\n+\n+        paradigms: pandas Dataframe or string or list of pandas DataFrames or\n+                   strings,\n+            fMRI paradigms used to build design matrices. One paradigm expected\n+            per run_img. Ignored in case designs is not None.\n+\n+        confounds: pandas Dataframe or string or list of pandas DataFrames or\n+                   strings,\n+            Each column in a DataFrame corresponds to a confound variable\n+            to be included in the regression model of the respective run_img.\n+            The number of rows must match the number of volumes in the\n+            respective run_img. Ignored in case designs is not None.\n+\n+        design_matrices: pandas DataFrame or list of pandas DataFrames,\n+            Design matrices that will be used to fit the GLM.\n+        \"\"\"\n+        # Check arguments\n+        if not isinstance(run_imgs, (list, tuple)):\n+            run_imgs = [run_imgs]\n+        for rimg in run_imgs:\n+            if not isinstance(rimg, (_basestring, Nifti1Image)):\n+                raise ValueError('run_imgs must be Niimg-like object or list'\n+                                 ' of Niimg-like objects')\n+\n+        if design_matrices is None:\n+            if paradigms is None:\n+                raise ValueError('paradigms or design matrices must be provided')\n+            if self.t_r is None:\n+                raise ValueError('t_r not given to FirstLevelModel object'\n+                                 ' to compute design from paradigm')\n+            if self.slice_time_ref is None:\n+                raise ValueError('slice_time_ref not given to FirstLevelModel'\n+                                 ' object to compute design from paradigm')\n+        else:\n+            design_matrices = _check_run_tables(run_imgs, design_matrices,\n+                                                'design_matrices')\n+\n+        if paradigms is not None:\n+            paradigms = _check_run_tables(run_imgs, paradigms, 'paradigms')\n+\n+        if confounds is not None:\n+            confounds = _check_run_tables(run_imgs, confounds, 'confounds')\n+\n+        # Learn the mask\n+        if not isinstance(self.mask, NiftiMasker):\n+            self.masker_ = NiftiMasker(\n+                mask_img=self.mask, smoothing_fwhm=self.smoothing_fwhm,\n+                target_affine=self.target_affine,\n+                standardize=self.standardize, low_pass=self.low_pass,\n+                high_pass=self.high_pass, mask_strategy='epi',\n+                t_r=self.t_r, memory=self.memory,\n+                verbose=max(0, self.verbose - 1),\n+                target_shape=self.target_shape,\n+                memory_level=self.memory_level)\n+        else:\n+            self.masker_ = clone(self.mask)\n+            for param_name in ['target_affine', 'target_shape',\n+                               'smoothing_fwhm', 'low_pass', 'high_pass',\n+                               't_r', 'memory', 'memory_level']:\n+                our_param = getattr(self, param_name)\n+                if our_param is None:\n+                    continue\n+                if getattr(self.masker_, param_name) is not None:\n+                    warn('Parameter %s of the masker overriden' % param_name)\n+                setattr(self.masker_, param_name, our_param)\n+        self.masker_.fit(run_imgs[0])\n+\n+        # For each run fit the model and keep only the regression results.\n+        self.labels_, self.results_, self.design_matrices_ = [], [], []\n+        n_runs = len(run_imgs)\n+        t0 = time.time()\n+        for run_idx, run_img in enumerate(run_imgs):\n+            # Report progress\n+            if self.verbose > 0:\n+                percent = float(run_idx) / n_runs\n+                percent = round(percent * 100, 2)\n+                dt = time.time() - t0\n+                # We use a max to avoid a division by zero\n+                if run_idx == 0:\n+                    remaining = 'estimating time remaining'\n+                else:\n+                    remaining = (100. - percent) / max(0.01, percent) * dt\n+                    remaining = '%i seconds remaining' % remaining\n+                sys.stderr.write(\n+                    \"Computing run %d out of %d runs (%s)\\r\"\n+                    % (run_idx, n_runs, remaining))\n+\n+            # Build the experimental design for the glm\n+            run_img = check_niimg(run_img, ensure_ndim=4)\n+            if design_matrices is None:\n+                n_scans = run_img.get_data().shape[3]\n+                if confounds is not None:\n+                    confounds_matrix = confounds[run_idx].values\n+                    if confounds_matrix.shape[0] != n_scans:\n+                        raise ValueError('Rows in confounds does not match'\n+                                         'n_scans in run_img at index %d'\n+                                         % (run_idx,))\n+                    confounds_names = confounds[run_idx].columns\n+                else:\n+                    confounds_matrix = None\n+                    confounds_names = None\n+                start_time = self.slice_time_ref * self.t_r\n+                end_time = (n_scans - self.slice_time_ref) * self.t_r\n+                frame_times = np.linspace(start_time, end_time, n_scans)\n+                design = make_design_matrix(frame_times, paradigms[run_idx],\n+                                            self.hrf_model, self.drift_model,\n+                                            self.period_cut, self.drift_order,\n+                                            self.fir_delays, confounds_matrix,\n+                                            confounds_names, self.min_onset)\n+            else:\n+                design = design_matrices[run_idx]\n+            self.design_matrices_.append(design)\n+\n+            # Compute GLM\n+            Y = self.masker_.transform(run_img)\n+            if self.signal_scaling:\n+                Y, _ = mean_scaling(Y, self.scaling_axis)\n+            if self.memory is not None:\n+                mem_glm = self.memory.cache(run_glm)\n+            else:\n+                mem_glm = run_glm\n+            labels, results = mem_glm(Y, design,\n+                                      noise_model=self.noise_model,\n+                                      bins=100, n_jobs=self.n_jobs)\n+            self.labels_.append(labels)\n+            # We save memory if inspecting model details is not necessary\n+            if self.minimize_memory:\n+                for key in results:\n+                    results[key] = SimpleRegressionResults(results[key])\n+            self.results_.append(results)\n+            del Y\n+\n+        # Report progress\n+        if self.verbose > 0:\n+            sys.stderr.write(\"\\nComputation of %d runs done in %i seconds\\n\"\n+                             % (n_runs, time.time() - t0))\n+\n+        return self\n+\n+    def get_design_matrices(self):\n+        \"\"\"Get design matrices computed during model fit\n+\n+        Returns\n+        -------\n+        design_matrices : list of pandas DataFrames,\n+            Holds the design matrices computed for the corresponding run_imgs\n+        \"\"\"\n+        if self.design_matrices_ is None:\n+            raise ValueError('The model has not been fit yet')\n+        else:\n+            return self.design_matrices_\n+\n+    def compute_contrast(self, contrast_def, contrast_name=None,\n+                         stat_type=None, output_type='z_score'):\n+        \"\"\"Generate different outputs corresponding to\n+        the contrasts provided e.g. z_map, t_map, effects and variance.\n+        In multi-session case, outputs the fixed effects map.\n+\n+        Parameters\n+        ----------\n+        contrast_def : array or list of arrays of shape (n_col) or (n_run, n_col)\n+            where ``n_col`` is the number of columns of the design matrix,\n+            (one array per run). If only one array is provided when there\n+            are several runs, it will be assumed that the same contrast is\n+            desired for all runs\n+\n+        contrast_name : str, optional\n+            name of the contrast\n+\n+        stat_type : {'t', 'F'}, optional\n+            type of the contrast\n+\n+        output_type : str, optional\n+            Type of the output map. Can be 'z_score', 'stat', 'p_value',\n+            'eff' or 'var'\n+\n+        Returns\n+        -------\n+        output_image : Nifti1Image\n+            The desired output image\n+\n+        \"\"\"\n+        if self.labels_ is None or self.results_ is None:\n+            raise ValueError('The model has not been fit yet')\n+\n+        if isinstance(contrast_def, np.ndarray):\n+            con_vals = [contrast_def]\n+        elif isinstance(contrast_def, (list, tuple)):\n+            con_vals = contrast_def\n+            for cidx, con in enumerate(contrast_def):\n+                if not isinstance(con, np.ndarray):\n+                    raise ValueError('contrast_def at index %i is not an'\n+                                     ' array' % cidx)\n+        else:\n+            raise ValueError('contrast_def must be an array')\n+        n_runs = len(self.labels_)\n+        if len(con_vals) != n_runs:\n+            warn('One contrast given, assuming it for all %d runs' % n_runs)\n+            con_vals = con_vals * n_runs\n+        if isinstance(output_type, _basestring):\n+            if output_type not in ['z_score', 'stat', 'p_value', 'eff', 'var']:\n+                raise ValueError('output_type must be \"z_score\", \"stat\",'","path":"nistats/first_level_model.py","position":539,"original_position":539,"commit_id":"2da923ed8afa1b6314f59c5c8cdb309d9e8209ca","original_commit_id":"2da923ed8afa1b6314f59c5c8cdb309d9e8209ca","user":{"login":"bthirion","id":234454,"avatar_url":"https://avatars.githubusercontent.com/u/234454?v=3","gravatar_id":"","url":"https://api.github.com/users/bthirion","html_url":"https://github.com/bthirion","followers_url":"https://api.github.com/users/bthirion/followers","following_url":"https://api.github.com/users/bthirion/following{/other_user}","gists_url":"https://api.github.com/users/bthirion/gists{/gist_id}","starred_url":"https://api.github.com/users/bthirion/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bthirion/subscriptions","organizations_url":"https://api.github.com/users/bthirion/orgs","repos_url":"https://api.github.com/users/bthirion/repos","events_url":"https://api.github.com/users/bthirion/events{/privacy}","received_events_url":"https://api.github.com/users/bthirion/received_events","type":"User","site_admin":false},"body":"must be one of","created_at":"2016-06-09T09:56:45Z","updated_at":"2016-06-09T09:56:45Z","html_url":"https://github.com/nistats/nistats/pull/72#discussion_r66412943","pull_request_url":"https://api.github.com/repos/nistats/nistats/pulls/72","_links":{"self":{"href":"https://api.github.com/repos/nistats/nistats/pulls/comments/66412943"},"html":{"href":"https://github.com/nistats/nistats/pull/72#discussion_r66412943"},"pull_request":{"href":"https://api.github.com/repos/nistats/nistats/pulls/72"}}},"pull_request":{"url":"https://api.github.com/repos/nistats/nistats/pulls/72","id":72789112,"html_url":"https://github.com/nistats/nistats/pull/72","diff_url":"https://github.com/nistats/nistats/pull/72.diff","patch_url":"https://github.com/nistats/nistats/pull/72.patch","issue_url":"https://api.github.com/repos/nistats/nistats/issues/72","number":72,"state":"open","locked":false,"title":"First level model","user":{"login":"MartinPerez","id":8766915,"avatar_url":"https://avatars.githubusercontent.com/u/8766915?v=3","gravatar_id":"","url":"https://api.github.com/users/MartinPerez","html_url":"https://github.com/MartinPerez","followers_url":"https://api.github.com/users/MartinPerez/followers","following_url":"https://api.github.com/users/MartinPerez/following{/other_user}","gists_url":"https://api.github.com/users/MartinPerez/gists{/gist_id}","starred_url":"https://api.github.com/users/MartinPerez/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MartinPerez/subscriptions","organizations_url":"https://api.github.com/users/MartinPerez/orgs","repos_url":"https://api.github.com/users/MartinPerez/repos","events_url":"https://api.github.com/users/MartinPerez/events{/privacy}","received_events_url":"https://api.github.com/users/MartinPerez/received_events","type":"User","site_admin":false},"body":"1. First level model object basic implementation ready. \r\n\r\n2. I added trivially modified examples newapi_localizer and newapi_fiac that will keep being modified as the roadmap evolves. To compare with real simple examples how things are changing. For now changes are minimal. The localizer example shows better that now the design matrix do not need to be precomputed, while the fiac example shows that previous behavior, passing directly design matrices, still works.\r\n\r\n3. memory issues were handled by releasing unnecessary variables and allowing the user to get rid of some regression results that are not useful unless the model will be inspected afterwards in details. The parameter minimize_memory in FirstLevelModel object takes care of this.\r\n\r\nImprovements over issue #70 can be appreciated\r\n\r\n![localizer](https://cloud.githubusercontent.com/assets/8766915/15839839/27d4ee6a-2c46-11e6-8df4-9289ef939d1f.png)\r\n\r\n![fiac](https://cloud.githubusercontent.com/assets/8766915/15839840/2abd836c-2c46-11e6-913b-f91256b89e1f.png)\r\n\r\n\r\n4. parallelization on OLS fit by voxel groups and on AR model by assigning each AR model to a job\r\n\r\n5. issue #44 addressed by adding scaling_axis parameter. renamed percent_signal_change to signal_scaling.\r\n\r\n6. Factored out contrasts functionality to their own script, for future coming improvements like formulas handling.\r\n\r\nNote: glm.py is supposed to be completely replaced by first_level_model.py and contrasts.py. Its kept such that previous examples keep working.\r\n\r\nPlease let me know what you think.","created_at":"2016-06-06T22:31:35Z","updated_at":"2016-06-09T09:56:45Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f64c75411772a0289b01ecee5e1fc83efd014e52","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/nistats/nistats/pulls/72/commits","review_comments_url":"https://api.github.com/repos/nistats/nistats/pulls/72/comments","review_comment_url":"https://api.github.com/repos/nistats/nistats/pulls/comments{/number}","comments_url":"https://api.github.com/repos/nistats/nistats/issues/72/comments","statuses_url":"https://api.github.com/repos/nistats/nistats/statuses/2da923ed8afa1b6314f59c5c8cdb309d9e8209ca","head":{"label":"MartinPerez:first_level_model","ref":"first_level_model","sha":"2da923ed8afa1b6314f59c5c8cdb309d9e8209ca","user":{"login":"MartinPerez","id":8766915,"avatar_url":"https://avatars.githubusercontent.com/u/8766915?v=3","gravatar_id":"","url":"https://api.github.com/users/MartinPerez","html_url":"https://github.com/MartinPerez","followers_url":"https://api.github.com/users/MartinPerez/followers","following_url":"https://api.github.com/users/MartinPerez/following{/other_user}","gists_url":"https://api.github.com/users/MartinPerez/gists{/gist_id}","starred_url":"https://api.github.com/users/MartinPerez/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MartinPerez/subscriptions","organizations_url":"https://api.github.com/users/MartinPerez/orgs","repos_url":"https://api.github.com/users/MartinPerez/repos","events_url":"https://api.github.com/users/MartinPerez/events{/privacy}","received_events_url":"https://api.github.com/users/MartinPerez/received_events","type":"User","site_admin":false},"repo":{"id":41736457,"name":"nistats","full_name":"MartinPerez/nistats","owner":{"login":"MartinPerez","id":8766915,"avatar_url":"https://avatars.githubusercontent.com/u/8766915?v=3","gravatar_id":"","url":"https://api.github.com/users/MartinPerez","html_url":"https://github.com/MartinPerez","followers_url":"https://api.github.com/users/MartinPerez/followers","following_url":"https://api.github.com/users/MartinPerez/following{/other_user}","gists_url":"https://api.github.com/users/MartinPerez/gists{/gist_id}","starred_url":"https://api.github.com/users/MartinPerez/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MartinPerez/subscriptions","organizations_url":"https://api.github.com/users/MartinPerez/orgs","repos_url":"https://api.github.com/users/MartinPerez/repos","events_url":"https://api.github.com/users/MartinPerez/events{/privacy}","received_events_url":"https://api.github.com/users/MartinPerez/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/MartinPerez/nistats","description":"Modeling and statistical inference on fMRI data in Python ","fork":true,"url":"https://api.github.com/repos/MartinPerez/nistats","forks_url":"https://api.github.com/repos/MartinPerez/nistats/forks","keys_url":"https://api.github.com/repos/MartinPerez/nistats/keys{/key_id}","collaborators_url":"https://api.github.com/repos/MartinPerez/nistats/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/MartinPerez/nistats/teams","hooks_url":"https://api.github.com/repos/MartinPerez/nistats/hooks","issue_events_url":"https://api.github.com/repos/MartinPerez/nistats/issues/events{/number}","events_url":"https://api.github.com/repos/MartinPerez/nistats/events","assignees_url":"https://api.github.com/repos/MartinPerez/nistats/assignees{/user}","branches_url":"https://api.github.com/repos/MartinPerez/nistats/branches{/branch}","tags_url":"https://api.github.com/repos/MartinPerez/nistats/tags","blobs_url":"https://api.github.com/repos/MartinPerez/nistats/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/MartinPerez/nistats/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/MartinPerez/nistats/git/refs{/sha}","trees_url":"https://api.github.com/repos/MartinPerez/nistats/git/trees{/sha}","statuses_url":"https://api.github.com/repos/MartinPerez/nistats/statuses/{sha}","languages_url":"https://api.github.com/repos/MartinPerez/nistats/languages","stargazers_url":"https://api.github.com/repos/MartinPerez/nistats/stargazers","contributors_url":"https://api.github.com/repos/MartinPerez/nistats/contributors","subscribers_url":"https://api.github.com/repos/MartinPerez/nistats/subscribers","subscription_url":"https://api.github.com/repos/MartinPerez/nistats/subscription","commits_url":"https://api.github.com/repos/MartinPerez/nistats/commits{/sha}","git_commits_url":"https://api.github.com/repos/MartinPerez/nistats/git/commits{/sha}","comments_url":"https://api.github.com/repos/MartinPerez/nistats/comments{/number}","issue_comment_url":"https://api.github.com/repos/MartinPerez/nistats/issues/comments{/number}","contents_url":"https://api.github.com/repos/MartinPerez/nistats/contents/{+path}","compare_url":"https://api.github.com/repos/MartinPerez/nistats/compare/{base}...{head}","merges_url":"https://api.github.com/repos/MartinPerez/nistats/merges","archive_url":"https://api.github.com/repos/MartinPerez/nistats/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/MartinPerez/nistats/downloads","issues_url":"https://api.github.com/repos/MartinPerez/nistats/issues{/number}","pulls_url":"https://api.github.com/repos/MartinPerez/nistats/pulls{/number}","milestones_url":"https://api.github.com/repos/MartinPerez/nistats/milestones{/number}","notifications_url":"https://api.github.com/repos/MartinPerez/nistats/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/MartinPerez/nistats/labels{/name}","releases_url":"https://api.github.com/repos/MartinPerez/nistats/releases{/id}","deployments_url":"https://api.github.com/repos/MartinPerez/nistats/deployments","created_at":"2015-09-01T12:12:28Z","updated_at":"2016-01-31T13:27:28Z","pushed_at":"2016-06-09T03:18:11Z","git_url":"git://github.com/MartinPerez/nistats.git","ssh_url":"git@github.com:MartinPerez/nistats.git","clone_url":"https://github.com/MartinPerez/nistats.git","svn_url":"https://github.com/MartinPerez/nistats","homepage":null,"size":370,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"nistats:master","ref":"master","sha":"b12921830599bb4c6edecdc838beec0c17eab785","user":{"login":"nistats","id":12428142,"avatar_url":"https://avatars.githubusercontent.com/u/12428142?v=3","gravatar_id":"","url":"https://api.github.com/users/nistats","html_url":"https://github.com/nistats","followers_url":"https://api.github.com/users/nistats/followers","following_url":"https://api.github.com/users/nistats/following{/other_user}","gists_url":"https://api.github.com/users/nistats/gists{/gist_id}","starred_url":"https://api.github.com/users/nistats/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nistats/subscriptions","organizations_url":"https://api.github.com/users/nistats/orgs","repos_url":"https://api.github.com/users/nistats/repos","events_url":"https://api.github.com/users/nistats/events{/privacy}","received_events_url":"https://api.github.com/users/nistats/received_events","type":"Organization","site_admin":false},"repo":{"id":35545854,"name":"nistats","full_name":"nistats/nistats","owner":{"login":"nistats","id":12428142,"avatar_url":"https://avatars.githubusercontent.com/u/12428142?v=3","gravatar_id":"","url":"https://api.github.com/users/nistats","html_url":"https://github.com/nistats","followers_url":"https://api.github.com/users/nistats/followers","following_url":"https://api.github.com/users/nistats/following{/other_user}","gists_url":"https://api.github.com/users/nistats/gists{/gist_id}","starred_url":"https://api.github.com/users/nistats/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nistats/subscriptions","organizations_url":"https://api.github.com/users/nistats/orgs","repos_url":"https://api.github.com/users/nistats/repos","events_url":"https://api.github.com/users/nistats/events{/privacy}","received_events_url":"https://api.github.com/users/nistats/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/nistats/nistats","description":"Modeling and statistical inference on fMRI data in Python ","fork":false,"url":"https://api.github.com/repos/nistats/nistats","forks_url":"https://api.github.com/repos/nistats/nistats/forks","keys_url":"https://api.github.com/repos/nistats/nistats/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nistats/nistats/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nistats/nistats/teams","hooks_url":"https://api.github.com/repos/nistats/nistats/hooks","issue_events_url":"https://api.github.com/repos/nistats/nistats/issues/events{/number}","events_url":"https://api.github.com/repos/nistats/nistats/events","assignees_url":"https://api.github.com/repos/nistats/nistats/assignees{/user}","branches_url":"https://api.github.com/repos/nistats/nistats/branches{/branch}","tags_url":"https://api.github.com/repos/nistats/nistats/tags","blobs_url":"https://api.github.com/repos/nistats/nistats/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nistats/nistats/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nistats/nistats/git/refs{/sha}","trees_url":"https://api.github.com/repos/nistats/nistats/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nistats/nistats/statuses/{sha}","languages_url":"https://api.github.com/repos/nistats/nistats/languages","stargazers_url":"https://api.github.com/repos/nistats/nistats/stargazers","contributors_url":"https://api.github.com/repos/nistats/nistats/contributors","subscribers_url":"https://api.github.com/repos/nistats/nistats/subscribers","subscription_url":"https://api.github.com/repos/nistats/nistats/subscription","commits_url":"https://api.github.com/repos/nistats/nistats/commits{/sha}","git_commits_url":"https://api.github.com/repos/nistats/nistats/git/commits{/sha}","comments_url":"https://api.github.com/repos/nistats/nistats/comments{/number}","issue_comment_url":"https://api.github.com/repos/nistats/nistats/issues/comments{/number}","contents_url":"https://api.github.com/repos/nistats/nistats/contents/{+path}","compare_url":"https://api.github.com/repos/nistats/nistats/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nistats/nistats/merges","archive_url":"https://api.github.com/repos/nistats/nistats/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nistats/nistats/downloads","issues_url":"https://api.github.com/repos/nistats/nistats/issues{/number}","pulls_url":"https://api.github.com/repos/nistats/nistats/pulls{/number}","milestones_url":"https://api.github.com/repos/nistats/nistats/milestones{/number}","notifications_url":"https://api.github.com/repos/nistats/nistats/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nistats/nistats/labels{/name}","releases_url":"https://api.github.com/repos/nistats/nistats/releases{/id}","deployments_url":"https://api.github.com/repos/nistats/nistats/deployments","created_at":"2015-05-13T11:44:31Z","updated_at":"2016-04-15T03:58:59Z","pushed_at":"2016-06-09T01:53:03Z","git_url":"git://github.com/nistats/nistats.git","ssh_url":"git@github.com:nistats/nistats.git","clone_url":"https://github.com/nistats/nistats.git","svn_url":"https://github.com/nistats/nistats","homepage":null,"size":339,"stargazers_count":14,"watchers_count":14,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":18,"mirror_url":null,"open_issues_count":35,"forks":18,"open_issues":35,"watchers":14,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/nistats/nistats/pulls/72"},"html":{"href":"https://github.com/nistats/nistats/pull/72"},"issue":{"href":"https://api.github.com/repos/nistats/nistats/issues/72"},"comments":{"href":"https://api.github.com/repos/nistats/nistats/issues/72/comments"},"review_comments":{"href":"https://api.github.com/repos/nistats/nistats/pulls/72/comments"},"review_comment":{"href":"https://api.github.com/repos/nistats/nistats/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/nistats/nistats/pulls/72/commits"},"statuses":{"href":"https://api.github.com/repos/nistats/nistats/statuses/2da923ed8afa1b6314f59c5c8cdb309d9e8209ca"}}}}
{"actor":{"display_login":"bthirion"}}