{"action":"created","comment":{"url":"https://api.github.com/repos/nilearn/nilearn/pulls/comments/91342526","pull_request_review_id":11851555,"id":91342526,"diff_hunk":"@@ -0,0 +1,2589 @@\n+\"\"\"\n+Download statistical maps available on Neurovault (http://neurovault.org).\n+\"\"\"\n+# Author: Jerome Dockes\n+# License: simplified BSD\n+\n+import os\n+import logging\n+import warnings\n+from copy import copy, deepcopy\n+import shutil\n+import re\n+import json\n+from glob import glob\n+from tempfile import mkdtemp\n+from collections import Container\n+try:\n+    from collections import OrderedDict\n+except ImportError:\n+    OrderedDict = dict\n+import traceback\n+try:\n+    from urllib.parse import urljoin, urlencode\n+    from urllib.request import build_opener, Request\n+    from urllib.error import URLError\n+except ImportError:\n+    from urlparse import urljoin\n+    from urllib import urlencode\n+    from urllib2 import build_opener, Request, URLError\n+\n+import numpy as np\n+from sklearn.datasets.base import Bunch\n+from sklearn.feature_extraction import DictVectorizer\n+\n+from .._utils.compat import _basestring\n+from .utils import _fetch_file, _get_dataset_dir, _get_dataset_descr\n+\n+\n+_NEUROVAULT_BASE_URL = 'http://neurovault.org/api/'\n+_NEUROVAULT_COLLECTIONS_URL = urljoin(_NEUROVAULT_BASE_URL, 'collections/')\n+_NEUROVAULT_IMAGES_URL = urljoin(_NEUROVAULT_BASE_URL, 'images/')\n+_NEUROSYNTH_FETCH_WORDS_URL = 'http://neurosynth.org/api/v2/decode/'\n+\n+_COL_FILTERS_AVAILABLE_ON_SERVER = ('DOI', 'name', 'owner', 'id')\n+_IM_FILTERS_AVAILABLE_ON_SERVER = tuple()\n+\n+_DEFAULT_BATCH_SIZE = 100\n+\n+\n+class MaxImagesReached(StopIteration):\n+    \"\"\"Exception class to signify enough images have been fetched.\"\"\"\n+    pass\n+\n+\n+def prepare_logging(level=logging.DEBUG):\n+    \"\"\"Get the root logger.\n+\n+    Parameters\n+    ----------\n+    level : int, optional (default=logging.DEBUG)\n+        Level of the handler that is added if none exist.\n+        this handler streams output to the console.\n+\n+    Returns\n+    -------\n+    logging.RootLogger\n+        The root logger.\n+\n+    \"\"\"\n+    logger = logging.getLogger()\n+    if logger.handlers:\n+        return logger\n+    logger.propagate = False\n+    logger.setLevel(logging.DEBUG)\n+    return logger\n+\n+\n+_logger = prepare_logging(level=logging.INFO)\n+\n+\n+def add_logging_to_console(level=logging.DEBUG):\n+    console_logger = logging.StreamHandler()\n+    console_logger.setLevel(level)\n+    formatter = logging.Formatter('%(message)s')\n+    console_logger.setFormatter(formatter)\n+    _logger.addHandler(console_logger)\n+\n+\n+def add_log_file(file_name, level=logging.DEBUG):\n+    file_logger = logging.FileHandler(file_name)\n+    file_logger.setLevel(level)\n+    formatter = logging.Formatter('%(asctime)s: %(message)s')\n+    file_logger.setFormatter(formatter)\n+    _logger.addHandler(file_logger)\n+\n+\n+def set_logging_level(level=logging.INFO):\n+    for handler in _logger.handlers:\n+        handler.setLevel(level)\n+\n+\n+\n+def _append_filters_to_query(query, filters):\n+    \"\"\"Encode dict or sequence of key-value pairs into a URL query string\n+\n+    Parameters\n+    ----------\n+    query : str\n+        URL to which the filters should be appended\n+\n+    filters : dict or sequence of pairs\n+        Filters to append to the URL.\n+\n+    Returns\n+    -------\n+    str\n+        The query with filters appended to it.\n+\n+    Notes\n+    -----\n+    If one of the `filters` keys is 'id', we get the url that points\n+    directly to that id,\n+    e.g. 'http://neurovault.org/api/collections/40', and the other\n+    filters are ignored.\n+\n+    \"\"\"\n+    if not filters:\n+        return query\n+    if 'id' in filters:\n+        return urljoin(query, str(filters['id']))\n+    new_query = urljoin(\n+        query, '?{0}'.format(urlencode(filters)))\n+    return new_query\n+\n+\n+def _empty_filter(arg):\n+    \"\"\"Place holder for a filter which always returns True.\"\"\"\n+    return True\n+\n+\n+def _get_encoding(resp):\n+    \"\"\"Get the encoding of an HTTP response.\n+\n+    Parameters\n+    ----------\n+    resp : http.client.HTTPResponse\n+        Response whose encoding we want to find out.\n+\n+    Returns\n+    -------\n+    str\n+        str representing the encoding, e.g. 'utf-8'.\n+\n+    Raises\n+    ------\n+    ValueError\n+        If the response does not specify an encoding.\n+\n+    \"\"\"\n+    try:\n+        charset = resp.headers.get_content_charset()\n+        if charset is not None:\n+            return charset\n+    except AttributeError:\n+        pass\n+    content_type = resp.headers.get('Content-Type', '')\n+    match = re.search(r'charset=\\b(.+)\\b', content_type)\n+    if match is None:\n+        raise ValueError(\n+            'HTTP response encoding not found; headers: {0}'.format(\n+                resp.headers))\n+    return match.group(1)\n+\n+\n+def _get_batch(query, prefix_msg='', timeout=10.):\n+    \"\"\"Given an URL, get the HTTP response and transform it to python dict.\n+\n+    The URL is used to send an HTTP GET request and the response is\n+    transformed into a dictionary.\n+\n+    Parameters\n+    ----------\n+    query : str\n+        The URL from which to get data.\n+\n+    prefix_msg : str, optional (default='')\n+        Prefix for all log messages.\n+\n+    timeout : float\n+        Timeout in seconds.\n+\n+    Returns\n+    -------\n+    batch : dict\n+        Python dict representing the response's content.\n+\n+    Raises\n+    ------\n+    urllib.error.URLError\n+        If there was a problem opening the URL.\n+\n+    ValueError\n+        If the response could not be decoded, or did not contain\n+        either 'id' (single result), or 'results' and 'count' (actual\n+        batch).\n+\n+    Notes\n+    -----\n+    urllib.error.HTTPError is a subclass of URLError.\n+\n+    \"\"\"\n+    request = Request(query)\n+    request.add_header('Connection', 'Keep-Alive')\n+    opener = build_opener()\n+    _logger.debug('{0}getting new batch: {1}'.format(\n+        prefix_msg, query))\n+    try:\n+        resp = opener.open(request, timeout=timeout)\n+\n+    except Exception:\n+        _logger.exception(\n+            'Could not download batch from {0}'.format(query))\n+        raise\n+    try:\n+        encoding = _get_encoding(resp)\n+        content = resp.read()\n+        batch = json.loads(content.decode(encoding))\n+    except(URLError, ValueError):\n+        _logger.exception('Could not decypher batch from {0}'.format(query))\n+        raise\n+    finally:\n+        resp.close()\n+    if 'id' in batch:\n+        batch = {'count': 1, 'results': [batch]}\n+    for key in ['results', 'count']:\n+        if batch.get(key) is None:\n+            msg = ('Could not find required key \"{0}\" '\n+                   'in batch retrieved from {1}'.format(key, query))\n+            _logger.error(msg)\n+            raise ValueError(msg)\n+\n+    return batch\n+\n+\n+def _scroll_server_results(url, local_filter=_empty_filter,\n+                           query_terms=None, max_results=None,\n+                           batch_size=None, prefix_msg=''):\n+    \"\"\"Download list of metadata from Neurovault.\n+\n+    Parameters\n+    ----------\n+    url : str\n+        The base url (without the filters) from which to get data.\n+\n+    local_filter : callable, optional (default=_empty_filter)\n+        Used to filter the results based on their metadata:\n+        must return True is the result is to be kept and False otherwise.\n+        Is called with the dict containing the metadata as sole argument.\n+\n+    query_terms : dict, sequence of pairs or None, optional (default=None)\n+        Key-value pairs to add to the base url in order to form query.\n+        If ``None``, nothing is added to the url.\n+\n+    max_results: int or None, optional (default=None)\n+        Maximum number of results to fetch; if ``None``, all available data\n+        that matches the query is fetched.\n+\n+    batch_size: int or None, optional (default=None)\n+        Neurovault returns the metadata for hits corresponding to a query\n+        in batches. batch_size is used to choose the (maximum) number of\n+        elements in a batch. If None, ``_DEFAULT_BATCH_SIZE`` is used.\n+\n+    prefix_msg: str, optional (default='')\n+        Prefix for all log messages.\n+\n+    Yields\n+    ------\n+    result : dict\n+        A result in the retrieved batch.\n+\n+    Raises\n+    ------\n+    URLError, ValueError\n+        If a batch failed to be retrieved.\n+\n+    \"\"\"\n+    query = _append_filters_to_query(url, query_terms)\n+    if batch_size is None:\n+        batch_size = _DEFAULT_BATCH_SIZE\n+    query = '{0}{1}limit={2}&offset={{0}}'.format(\n+        query, ('&' if '?' in query else '?'), batch_size)\n+    downloaded = 0\n+    n_available = None\n+    while(max_results is None or downloaded < max_results):\n+        new_query = query.format(downloaded)\n+        batch = _get_batch(new_query, prefix_msg)\n+        batch_size = len(batch['results'])\n+        downloaded += batch_size\n+        _logger.debug('{0}batch size: {1}'.format(prefix_msg, batch_size))\n+        if n_available is None:\n+            n_available = batch['count']\n+            max_results = (n_available if max_results is None\n+                           else min(max_results, n_available))\n+        for result in batch['results']:\n+            if local_filter(result):\n+                yield result\n+\n+\n+def _yield_from_url_list(url_list):\n+    \"\"\"Get metadata coming from an explicit list of URLs.\n+\n+    This is different from ``_scroll_server_results``, which is used\n+    to get all the metadata that matches certain filters.\n+\n+    Parameters\n+    ----------\n+    url_list : Container of str\n+        URLs from which to get data\n+\n+    Yields\n+    ------\n+    content : dict\n+        The metadata from one URL.\n+\n+    Raises\n+    ------\n+    urllib.error.URLError\n+        If there was a problem opening an URL.\n+\n+    ValueError\n+        If a response could not be decoded, or did not contain either\n+        'id' (single result), or 'results' and 'count' (batch).\n+\n+    \"\"\"\n+    for url in url_list:\n+        content = _get_batch(url)['results'][0]\n+        yield content\n+\n+\n+class _SpecialValue(object):\n+    \"\"\"Base class for special values used to filter terms.\n+\n+    Derived classes should override ``__eq__`` in order to create\n+    objects that can be used for comparisons to particular sets of\n+    values in filters.\n+\n+    \"\"\"\n+    def __eq__(self, other):\n+        raise NotImplementedError('Use a derived class for _SpecialValue')\n+\n+    def __req__(self, other):\n+        return self.__eq__(other)\n+\n+    def __ne__(self, other):\n+        return not self.__eq__(other)\n+\n+    def __rne__(self, other):\n+        return self.__ne__(other)\n+\n+    def __repr__(self):\n+        if hasattr(self, 'repr_arg_'):\n+            return '{0}({1!r})'.format(self.__class__.__name__, self.repr_arg_)\n+        return '{0}()'.format(self.__class__.__name__)\n+\n+\n+class IsNull(_SpecialValue):\n+    \"\"\"Special value used to filter terms.\n+\n+    An instance of this class will always be equal to, and only to,\n+    any null value of any type (by null we mean for which bool\n+    returns False).\n+\n+    See Also\n+    --------\n+    nilearn.datasets.neurovault.NotNull,\n+    nilearn.datasets.neurovault.NotEqual,\n+    nilearn.datasets.neurovault.GreaterOrEqual,\n+    nilearn.datasets.neurovault.GreaterThan,\n+    nilearn.datasets.neurovault.LessOrEqual,\n+    nilearn.datasets.neurovault.LessThan,\n+    nilearn.datasets.neurovault.IsIn,\n+    nilearn.datasets.neurovault.NotIn,\n+    nilearn.datasets.neurovault.Contains,\n+    nilearn.datasets.neurovault.NotContains,\n+    nilearn.datasets.neurovault.Pattern.\n+\n+    \"\"\"\n+    def __eq__(self, other):\n+        return not bool(other)\n+\n+\n+class NotNull(_SpecialValue):\n+    \"\"\"Special value used to filter terms.\n+\n+    An instance of this class will always be equal to, and only to,\n+    any non-zero value of any type (by non-zero we mean for which bool\n+    returns True).\n+\n+    See Also\n+    --------\n+    nilearn.datasets.neurovault.IsNull,\n+    nilearn.datasets.neurovault.NotEqual,\n+    nilearn.datasets.neurovault.GreaterOrEqual,\n+    nilearn.datasets.neurovault.GreaterThan,\n+    nilearn.datasets.neurovault.LessOrEqual,\n+    nilearn.datasets.neurovault.LessThan,\n+    nilearn.datasets.neurovault.IsIn,\n+    nilearn.datasets.neurovault.NotIn,\n+    nilearn.datasets.neurovault.Contains,\n+    nilearn.datasets.neurovault.NotContains,\n+    nilearn.datasets.neurovault.Pattern.\n+\n+    \"\"\"\n+    def __eq__(self, other):\n+        return bool(other)\n+\n+\n+class NotEqual(_SpecialValue):\n+    \"\"\"Special value used to filter terms.\n+\n+    An instance of this class is constructed with `NotEqual(obj)`. It\n+    will allways be equal to, and only to, any value for which\n+    ``obj == value`` is ``False``.\n+\n+    Parameters\n+    ----------\n+    negated : object\n+        The object from which a candidate should be different in order\n+        to pass through the filter.\n+\n+    See Also\n+    --------\n+    nilearn.datasets.neurovault.IsNull,\n+    nilearn.datasets.neurovault.NotNull,\n+    nilearn.datasets.neurovault.GreaterOrEqual,\n+    nilearn.datasets.neurovault.GreaterThan,\n+    nilearn.datasets.neurovault.LessOrEqual,\n+    nilearn.datasets.neurovault.LessThan,\n+    nilearn.datasets.neurovault.IsIn,\n+    nilearn.datasets.neurovault.NotIn,\n+    nilearn.datasets.neurovault.Contains,\n+    nilearn.datasets.neurovault.NotContains,\n+    nilearn.datasets.neurovault.Pattern.\n+\n+    \"\"\"\n+    def __init__(self, negated):\n+        self.negated_ = negated\n+        self.repr_arg_ = self.negated_\n+\n+    def __eq__(self, other):\n+        return not self.negated_ == other\n+\n+\n+class _OrderComp(_SpecialValue):\n+    \"\"\"Base class for special values based on order comparisons.\"\"\"\n+    def __init__(self, bound):\n+        self.bound_ = bound\n+        self._cast = type(bound)\n+        self.repr_arg_ = self.bound_\n+\n+    def __eq__(self, other):\n+        try:\n+            return self._eq_impl(self._cast(other))\n+        except (TypeError, ValueError):\n+            return False\n+\n+\n+class GreaterOrEqual(_OrderComp):\n+    \"\"\"Special value used to filter terms.\n+\n+    An instance of this class is constructed with `GreaterOrEqual(obj)`. It\n+    will allways be equal to, and only to, any value for which\n+    ``obj <= value`` is ``True``.\n+\n+    Parameters\n+    ----------\n+    bound : object\n+        The object to which a candidate should be superior or equal in\n+        order to pass through the filter.\n+\n+    See Also\n+    --------\n+    nilearn.datasets.neurovault.IsNull,\n+    nilearn.datasets.neurovault.NotNull,\n+    nilearn.datasets.neurovault.NotEqual,\n+    nilearn.datasets.neurovault.GreaterThan,\n+    nilearn.datasets.neurovault.LessOrEqual,\n+    nilearn.datasets.neurovault.LessThan,\n+    nilearn.datasets.neurovault.IsIn,\n+    nilearn.datasets.neurovault.NotIn,\n+    nilearn.datasets.neurovault.Contains,\n+    nilearn.datasets.neurovault.NotContains,\n+    nilearn.datasets.neurovault.Pattern.\n+\n+    \"\"\"\n+    def _eq_impl(self, other):\n+        return self.bound_ <= other\n+\n+\n+class GreaterThan(_OrderComp):\n+    \"\"\"Special value used to filter terms.\n+\n+    An instance of this class is constructed with `GreaterThan(obj)`. It\n+    will allways be equal to, and only to, any value for which\n+    ``obj < value`` is ``True``.\n+\n+    Parameters\n+    ----------\n+    bound : object\n+        The object to which a candidate should be strictly superior in\n+        order to pass through the filter.\n+\n+    See Also\n+    --------\n+    nilearn.datasets.neurovault.IsNull,\n+    nilearn.datasets.neurovault.NotNull,\n+    nilearn.datasets.neurovault.NotEqual,\n+    nilearn.datasets.neurovault.GreaterOrEqual,\n+    nilearn.datasets.neurovault.LessOrEqual,\n+    nilearn.datasets.neurovault.LessThan,\n+    nilearn.datasets.neurovault.IsIn,\n+    nilearn.datasets.neurovault.NotIn,\n+    nilearn.datasets.neurovault.Contains,\n+    nilearn.datasets.neurovault.NotContains,\n+    nilearn.datasets.neurovault.Pattern.\n+\n+    \"\"\"\n+    def _eq_impl(self, other):\n+        return self.bound_ < other\n+\n+\n+class LessOrEqual(_OrderComp):\n+    \"\"\"Special value used to filter terms.\n+\n+    An instance of this class is constructed with `LessOrEqual(obj)`. It\n+    will allways be equal to, and only to, any value for which\n+    ``value <= obj`` is ``True``.\n+\n+    Parameters\n+    ----------\n+    bound : object\n+        The object to which a candidate should be inferior or equal in\n+        order to pass through the filter.\n+\n+    See Also\n+    --------\n+    nilearn.datasets.neurovault.IsNull,\n+    nilearn.datasets.neurovault.NotNull,\n+    nilearn.datasets.neurovault.NotEqual,\n+    nilearn.datasets.neurovault.GreaterOrEqual,\n+    nilearn.datasets.neurovault.GreaterThan,\n+    nilearn.datasets.neurovault.LessThan,\n+    nilearn.datasets.neurovault.IsIn,\n+    nilearn.datasets.neurovault.NotIn,\n+    nilearn.datasets.neurovault.Contains,\n+    nilearn.datasets.neurovault.NotContains,\n+    nilearn.datasets.neurovault.Pattern.\n+\n+    \"\"\"\n+    def _eq_impl(self, other):\n+        return other <= self.bound_\n+\n+\n+class LessThan(_OrderComp):\n+    \"\"\"Special value used to filter terms.\n+\n+    An instance of this class is constructed with `LessThan(obj)`. It\n+    will allways be equal to, and only to, any value for which\n+    ``value < obj`` is ``True``.\n+\n+    Parameters\n+    ----------\n+    bound : object\n+        The object to which a candidate should be strictly inferior in\n+        order to pass through the filter.\n+\n+    See Also\n+    --------\n+    nilearn.datasets.neurovault.IsNull,\n+    nilearn.datasets.neurovault.NotNull,\n+    nilearn.datasets.neurovault.NotEqual,\n+    nilearn.datasets.neurovault.GreaterOrEqual,\n+    nilearn.datasets.neurovault.GreaterThan,\n+    nilearn.datasets.neurovault.LessOrEqual,\n+    nilearn.datasets.neurovault.IsIn,\n+    nilearn.datasets.neurovault.NotIn,\n+    nilearn.datasets.neurovault.Contains,\n+    nilearn.datasets.neurovault.NotContains,\n+    nilearn.datasets.neurovault.Pattern.\n+\n+    \"\"\"\n+    def _eq_impl(self, other):\n+        return other < self.bound_\n+\n+\n+class IsIn(_SpecialValue):\n+    \"\"\"Special value used to filter terms.\n+\n+    An instance of this class is constructed with\n+    `IsIn(*accepted)`. It will allways be equal to, and only to, any\n+    value for which ``value in accepted`` is ``True``.\n+\n+    Parameters\n+    ----------\n+    accepted : container\n+        A value will pass through the filter if it is present in\n+        `accepted`.\n+\n+    See Also\n+    --------\n+    nilearn.datasets.neurovault.IsNull,\n+    nilearn.datasets.neurovault.NotNull,\n+    nilearn.datasets.neurovault.NotEqual,\n+    nilearn.datasets.neurovault.GreaterOrEqual,\n+    nilearn.datasets.neurovault.GreaterThan,\n+    nilearn.datasets.neurovault.LessOrEqual,\n+    nilearn.datasets.neurovault.LessThan,\n+    nilearn.datasets.neurovault.NotIn,\n+    nilearn.datasets.neurovault.Contains,\n+    nilearn.datasets.neurovault.NotContains,\n+    nilearn.datasets.neurovault.Pattern.\n+\n+    Examples\n+    --------\n+    >>> from nilearn.datasets.neurovault import IsIn\n+    >>> countable = IsIn(*range(11))\n+    >>> 7 == countable\n+    True\n+    >>> countable == 12\n+    False\n+\n+    \"\"\"\n+    def __init__(self, *accepted):\n+        self.accepted_ = accepted\n+\n+    def __eq__(self, other):\n+        return other in self.accepted_\n+\n+    def __repr__(self):\n+        return '{0}{1!r}'.format(\n+            self.__class__.__name__, self.accepted_)\n+\n+\n+class NotIn(_SpecialValue):\n+    \"\"\"Special value used to filter terms.\n+\n+    An instance of this class is constructed with\n+    `NotIn(*rejected)`. It will allways be equal to, and only to, any\n+    value for which ``value in rejected`` is ``False``.\n+\n+    Parameters\n+    ----------\n+    rejected : container\n+        A value will pass through the filter if it is absent from\n+        `rejected`.\n+\n+    See Also\n+    --------\n+    nilearn.datasets.neurovault.IsNull,\n+    nilearn.datasets.neurovault.NotNull,\n+    nilearn.datasets.neurovault.NotEqual,\n+    nilearn.datasets.neurovault.GreaterOrEqual,\n+    nilearn.datasets.neurovault.GreaterThan,\n+    nilearn.datasets.neurovault.LessOrEqual,\n+    nilearn.datasets.neurovault.LessThan,\n+    nilearn.datasets.neurovault.IsIn,\n+    nilearn.datasets.neurovault.Contains,\n+    nilearn.datasets.neurovault.NotContains,\n+    nilearn.datasets.neurovault.Pattern.\n+\n+    \"\"\"\n+    def __init__(self, *rejected):\n+        self.rejected_ = rejected\n+\n+    def __eq__(self, other):\n+        return other not in self.rejected_\n+\n+    def __repr__(self):\n+        return '{0}{1!r}'.format(\n+            self.__class__.__name__, self.rejected_)\n+\n+\n+class Contains(_SpecialValue):\n+    \"\"\"Special value used to filter terms.\n+\n+    An instance of this class is constructed with\n+    `Contains(*must_be_contained)`. It will allways be equal to, and\n+    only to, any value for which ``item in value`` is ``True`` for\n+    every item in ``must_be_contained``.\n+\n+    Parameters\n+    ----------\n+    must_be_contained : container\n+        A value will pass through the filter if it contains all the\n+        items in must_be_contained.\n+\n+    See Also\n+    --------\n+    nilearn.datasets.neurovault.IsNull,\n+    nilearn.datasets.neurovault.NotNull,\n+    nilearn.datasets.neurovault.NotEqual,\n+    nilearn.datasets.neurovault.GreaterOrEqual,\n+    nilearn.datasets.neurovault.GreaterThan,\n+    nilearn.datasets.neurovault.LessOrEqual,\n+    nilearn.datasets.neurovault.LessThan,\n+    nilearn.datasets.neurovault.IsIn,\n+    nilearn.datasets.neurovault.NotIn,\n+    nilearn.datasets.neurovault.NotContains,\n+    nilearn.datasets.neurovault.Pattern.\n+\n+    Examples\n+    --------\n+    >>> from nilearn.datasets.neurovault import Contains\n+    >>> contains = Contains('house', 'face')\n+    >>> 'face vs house' == contains\n+    True\n+    >>> 'smiling face vs frowning face' == contains\n+    False\n+\n+    \"\"\"\n+    def __init__(self, *must_be_contained):\n+        self.must_be_contained_ = must_be_contained\n+\n+    def __eq__(self, other):\n+        if not isinstance(other, Container):\n+            return False\n+        for item in self.must_be_contained_:\n+            if item not in other:\n+                return False\n+        return True\n+\n+    def __repr__(self):\n+        return '{0}{1!r}'.format(\n+            self.__class__.__name__, self.must_be_contained_)\n+\n+\n+class NotContains(_SpecialValue):\n+    \"\"\"Special value used to filter terms.\n+\n+    An instance of this class is constructed with\n+    `NotContains(*must_not_be_contained)`. It will allways be equal\n+    to, and only to, any value for which ``item in value`` is\n+    ``False`` for every item in ``must_not_be_contained``.\n+\n+    Parameters\n+    ----------\n+    must_not_be_contained : container\n+        A value will pass through the filter if it does not contain\n+        any of the items in must_not_be_contained.\n+\n+    See Also\n+    --------\n+    nilearn.datasets.neurovault.IsNull,\n+    nilearn.datasets.neurovault.NotNull,\n+    nilearn.datasets.neurovault.NotEqual,\n+    nilearn.datasets.neurovault.GreaterOrEqual,\n+    nilearn.datasets.neurovault.GreaterThan,\n+    nilearn.datasets.neurovault.LessOrEqual,\n+    nilearn.datasets.neurovault.LessThan,\n+    nilearn.datasets.neurovault.IsIn,\n+    nilearn.datasets.neurovault.NotIn,\n+    nilearn.datasets.neurovault.Contains,\n+    nilearn.datasets.neurovault.Pattern.\n+\n+    \"\"\"\n+    def __init__(self, *must_not_be_contained):\n+        self.must_not_be_contained_ = must_not_be_contained\n+\n+    def __eq__(self, other):\n+        if not isinstance(other, Container):\n+            return False\n+        for item in self.must_not_be_contained_:\n+            if item in other:\n+                return False\n+        return True\n+\n+    def __repr__(self):\n+        return '{0}{1!r}'.format(\n+            self.__class__.__name__, self.must_not_be_contained_)\n+\n+\n+class Pattern(_SpecialValue):\n+    \"\"\"Special value used to filter terms.\n+\n+    An instance of this class is constructed with\n+\n+    `Pattern(pattern[, flags])`. It will allways be equal to, and only\n+    to, any value for which ``re.match(pattern, value, flags)`` is\n+    ``True``.\n+\n+    Parameters\n+    ----------\n+    pattern : str\n+        The pattern to try to match to candidates.\n+\n+    flags : int, optional (default=0)\n+        Value for ``re.match`` `flags` parameter,\n+        e.g. ``re.IGNORECASE``. The default (0), is the default value\n+        used by ``re.match``.\n+\n+    See Also\n+    --------\n+    nilearn.datasets.neurovault.IsNull,\n+    nilearn.datasets.neurovault.NotNull,\n+    nilearn.datasets.neurovault.NotEqual,\n+    nilearn.datasets.neurovault.GreaterOrEqual,\n+    nilearn.datasets.neurovault.GreaterThan,\n+    nilearn.datasets.neurovault.LessOrEqual,\n+    nilearn.datasets.neurovault.LessThan,\n+    nilearn.datasets.neurovault.IsIn,\n+    nilearn.datasets.neurovault.NotIn,\n+    nilearn.datasets.neurovault.Contains,\n+    nilearn.datasets.neurovault.NotContains.\n+\n+    Documentation for standard library ``re`` module.\n+\n+    Examples\n+    --------\n+    >>> from nilearn.datasets.neurovault import Pattern\n+    >>> pattern = Pattern(r'[0-9akqj]{5}$')\n+    >>> 'ak05q' == pattern\n+    True\n+    >>> 'ak05e' == pattern\n+    False\n+\n+    \"\"\"\n+    def __init__(self, pattern, flags=0):\n+        # Don't use re.compile because compiled patterns\n+        # can't be deepcopied.\n+        self.pattern_ = pattern\n+        self.flags_ = flags\n+\n+    def __eq__(self, other):\n+        if not isinstance(other, _basestring) or re.match(\n+                self.pattern_, other, self.flags_) is None:\n+            return False\n+        return True\n+\n+    def __repr__(self):\n+        return '{0}(pattern={1!r}, flags={2})'.format(\n+            self.__class__.__name__, self.pattern_, self.flags_)\n+\n+\n+class ResultFilter(object):\n+\n+    \"\"\"Easily create callable (local) filters for ``fetch_neurovault``.\n+\n+    Constructed from a mapping of key-value pairs (optional) and a\n+    callable filter (also optional), instances of this class are meant\n+    to be used as ``image_filter`` or ``collection_filter`` parameters\n+    for ``fetch_neurovault``.\n+\n+    Such filters can be combined using their methods ``AND``, ``OR``,\n+    ``XOR``, and ``NOT``, with the usual semantics.\n+\n+    Key-value pairs can be added by treating a ``ResultFilter`` as a\n+    dictionary: after evaluating ``res_filter[key] = value``, only\n+    metadata such that ``metadata[key] == value`` can pass through the\n+    filter.\n+\n+    Parameters\n+    ----------\n+\n+    query_terms : dict, optional (default=None)\n+        A ``metadata`` dictionary will be blocked by the filter if it\n+        does not respect ``metadata[key] == value`` for all\n+        ``key``, ``value`` pairs in `query_terms`. If ``None``, the\n+        empty dictionary is used.\n+\n+    callable_filter : callable, optional (default=_empty_filter)\n+        A ``metadata`` dictionary will be blocked by the filter if\n+        `callable_filter` does not return ``True`` for ``metadata``.\n+\n+    As an alternative to the `query_terms` dictionary parameter,\n+    key, value pairs can be passed as keyword arguments.\n+\n+    Attributes\n+    ----------\n+    query_terms_ : dict\n+        In order to pass through the filter, metadata must verify\n+        ``metadata[key] == value`` for each ``key``, ``value`` pair in\n+        `query_terms_`.\n+\n+    callable_filters_ : list of callables\n+        In addition to ``(key, value)`` pairs, we can use this\n+        attribute to specify more elaborate requirements. Called with\n+        a dict representing metadata for an image or collection, each\n+        element of this list returns ``True`` if the metadata should\n+        pass through the filter and ``False`` otherwise.\n+\n+    A dict of metadata will only pass through the filter if it\n+    satisfies all the `query_terms` AND all the elements of\n+    `callable_filters_`.\n+\n+    See Also\n+    --------\n+    nilearn.datasets.neurovault.IsNull,\n+    nilearn.datasets.neurovault.NotNull,\n+    nilearn.datasets.neurovault.NotEqual,\n+    nilearn.datasets.neurovault.GreaterOrEqual,\n+    nilearn.datasets.neurovault.GreaterThan,\n+    nilearn.datasets.neurovault.LessOrEqual,\n+    nilearn.datasets.neurovault.LessThan,\n+    nilearn.datasets.neurovault.IsIn,\n+    nilearn.datasets.neurovault.NotIn,\n+    nilearn.datasets.neurovault.Contains,\n+    nilearn.datasets.neurovault.NotContains,\n+    nilearn.datasets.neurovault.Pattern.\n+\n+    Examples\n+    --------\n+    >>> from nilearn.datasets.neurovault import ResultFilter\n+    >>> filt = ResultFilter(a=0).AND(ResultFilter(b=1).OR(ResultFilter(b=2)))\n+    >>> filt({'a': 0, 'b': 1})\n+    True\n+    >>> filt({'a': 0, 'b': 0})\n+    False\n+\n+    \"\"\"\n+\n+    def __init__(self, query_terms=None,\n+                 callable_filter=_empty_filter, **kwargs):\n+        if query_terms is None:\n+            query_terms = {}\n+        query_terms = dict(query_terms, **kwargs)\n+        self.query_terms_ = query_terms\n+        self.callable_filters_ = [callable_filter]\n+\n+    def __call__(self, candidate):\n+        \"\"\"Return True if candidate satisfies the requirements.\n+\n+        Parameters\n+        ----------\n+        candidate : dict\n+            A dictionary representing metadata for a file or a\n+            collection, to be filtered.\n+\n+        Returns\n+        -------\n+        bool\n+            ``True`` if `candidate` passes through the filter and ``False``\n+            otherwise.\n+\n+        \"\"\"\n+        for key, value in self.query_terms_.items():\n+            if not (value == candidate.get(key)):\n+                return False\n+        for callable_filter in self.callable_filters_:\n+            if not callable_filter(candidate):\n+                return False\n+        return True\n+\n+    def OR(self, other_filter):\n+        filt1, filt2 = deepcopy(self), deepcopy(other_filter)\n+        new_filter = ResultFilter(\n+            callable_filter=lambda r: filt1(r) or filt2(r))\n+        return new_filter\n+\n+    def AND(self, other_filter):\n+        filt1, filt2 = deepcopy(self), deepcopy(other_filter)\n+        new_filter = ResultFilter(\n+            callable_filter=lambda r: filt1(r) and filt2(r))\n+        return new_filter\n+\n+    def XOR(self, other_filter):\n+        filt1, filt2 = deepcopy(self), deepcopy(other_filter)\n+        new_filter = ResultFilter(\n+            callable_filter=lambda r: filt1(r) != filt2(r))\n+        return new_filter\n+\n+    def NOT(self):\n+        filt = deepcopy(self)\n+        new_filter = ResultFilter(\n+            callable_filter=lambda r: not filt(r))\n+        return new_filter\n+\n+    def __getitem__(self, item):\n+        \"\"\"Get item from query_terms_\"\"\"\n+        return self.query_terms_[item]\n+\n+    def __setitem__(self, item, value):\n+        \"\"\"Set item in query_terms_\"\"\"\n+        self.query_terms_[item] = value\n+\n+    def __delitem__(self, item):\n+        \"\"\"Remove item from query_terms\"\"\"\n+        if item in self.query_terms_:\n+            del self.query_terms_[item]\n+\n+    def add_filter(self, callable_filter):\n+        \"\"\"Add a function to the callable_filters_.\n+\n+        After a call add_filter(additional_filt), in addition to all\n+        the previous requirements, a candidate must also verify\n+        additional_filt(candidate) in order to pass through the\n+        filter.\n+\n+        \"\"\"\n+        self.callable_filters_.append(callable_filter)\n+\n+    def __str__(self):\n+        return self.__class__.__name__\n+\n+\n+def _simple_download(url, target_file, temp_dir):\n+    \"\"\"Wrapper around ``utils._fetch_file``.\n+\n+    This allows specifying the target file name.\n+\n+    Parameters\n+    ----------\n+    url : str\n+        URL of the file to download.\n+\n+    target_file : str\n+        Location of the downloaded file on filesystem.\n+\n+    temp_dir : str\n+        Location of sandbox directory used by ``_fetch_file``.\n+\n+    Returns\n+    -------\n+    target_file : str\n+        The location in which the file was downloaded.\n+\n+    Raises\n+    ------\n+    URLError, ValueError\n+        If an error occurred when downloading the file.\n+\n+    See Also\n+    --------\n+    nilearn.datasets._utils._fetch_file\n+\n+\n+    Notes\n+    -----\n+    It can happen that an HTTP error that occurs inside\n+    ``_fetch_file`` gets transformed into an ``AttributeError`` when\n+    we try to set the ``reason`` attribute of the exception raised;\n+    here we replace it with an ``URLError``.\n+\n+    \"\"\"\n+    print('Downloading file {} to {}'.format(url, target_file))\n+    _logger.debug('Downloading file: {0}'.format(url))\n+    try:\n+        downloaded = _fetch_file(url, temp_dir, resume=False,\n+                                 overwrite=True, verbose=0)\n+    except Exception as e:\n+        _logger.error('Problem downloading file from {0}'.format(url))\n+\n+        # reason is a property of urlib.error.HTTPError objects,\n+        # but these objects don't have a setter for it, so\n+        # an HTTPError raised in _fetch_file might be transformed\n+        # into an AttributeError when we try to set its reason attribute\n+        if (isinstance(e, AttributeError) and\n+            e.args[0] == \"can't set attribute\"):\n+            raise URLError(\n+                'HTTPError raised in nilearn.datasets._fetch_file: {0}'.format(\n+                    traceback.format_exc()))\n+        raise\n+    shutil.move(downloaded, target_file)\n+    _logger.debug(\n+        'Download succeeded, downloaded to: {0}'.format(target_file))\n+    return target_file\n+\n+\n+def _checked_get_dataset_dir(dataset_name, suggested_dir=None,\n+                             write_required=False):\n+    \"\"\"Wrapper for ``_get_dataset_dir``.\n+\n+    Expands . and ~ and checks write access.\n+\n+    Parameters\n+    ----------\n+    dataset_name : str\n+        Passed to ``_get_dataset_dir``. Example: ``neurovault``.\n+\n+    suggested_dir : str\n+        Desired location of root data directory for all datasets,\n+        e.g. ``~/home/nilearn_data``.\n+\n+    write_required : bool, optional (default=False)\n+        If ``True``, check that the user has write access to the\n+        chosen data directory and raise ``IOError`` if not.  If\n+        ``False``, don't check for write permission.\n+\n+    Returns\n+    -------\n+    dataset_dir : str\n+        The location of the dataset directory in the filesystem.\n+\n+    Raises\n+    ------\n+    IOError\n+        If `write_required` is set and the user doesn't have write\n+        access to `dataset_dir`.\n+\n+    See Also\n+    --------\n+    nilearns.datasets._utils._get_dataset_dir\n+\n+    \"\"\"\n+    if suggested_dir is not None:\n+        suggested_dir = os.path.abspath(os.path.expanduser(suggested_dir))\n+    dataset_dir = _get_dataset_dir(dataset_name, data_dir=suggested_dir)\n+    if not write_required:\n+        return dataset_dir\n+    if not os.access(dataset_dir, os.W_OK):\n+        raise IOError('Permission denied: {0}'.format(dataset_dir))\n+    return dataset_dir\n+\n+\n+def neurovault_directory(suggested_dir=None):\n+    \"\"\"Return path to neurovault directory on filesystem.\"\"\"\n+    if getattr(neurovault_directory, 'directory_path_', None) is not None:\n+        return neurovault_directory.directory_path_\n+\n+    _logger.debug('Looking for Neurovault directory.')\n+    if suggested_dir is None:\n+        root_data_dir, dataset_name = None, 'neurovault'\n+    else:\n+        suggested_path = suggested_dir.split(os.path.sep)\n+        dataset_name = suggested_path[-1]\n+        root_data_dir = os.path.sep.join(suggested_path[:-1])\n+    neurovault_directory.directory_path_ = _checked_get_dataset_dir(\n+        dataset_name, root_data_dir)\n+    assert(neurovault_directory.directory_path_ is not None)\n+    _logger.debug('Found Neurovault directory in {0}'.format(\n+        neurovault_directory.directory_path_))\n+    return neurovault_directory.directory_path_\n+\n+\n+def set_neurovault_directory(new_neurovault_dir=None):","path":"nilearn/datasets/neurovault.py","position":1134,"original_position":1134,"commit_id":"78155063966a21a9337a77121b986bcba2222455","original_commit_id":"78155063966a21a9337a77121b986bcba2222455","user":{"login":"GaelVaroquaux","id":208217,"avatar_url":"https://avatars.githubusercontent.com/u/208217?v=3","gravatar_id":"","url":"https://api.github.com/users/GaelVaroquaux","html_url":"https://github.com/GaelVaroquaux","followers_url":"https://api.github.com/users/GaelVaroquaux/followers","following_url":"https://api.github.com/users/GaelVaroquaux/following{/other_user}","gists_url":"https://api.github.com/users/GaelVaroquaux/gists{/gist_id}","starred_url":"https://api.github.com/users/GaelVaroquaux/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GaelVaroquaux/subscriptions","organizations_url":"https://api.github.com/users/GaelVaroquaux/orgs","repos_url":"https://api.github.com/users/GaelVaroquaux/repos","events_url":"https://api.github.com/users/GaelVaroquaux/events{/privacy}","received_events_url":"https://api.github.com/users/GaelVaroquaux/received_events","type":"User","site_admin":false},"body":"Let's remove this functionality.","created_at":"2016-12-07T17:24:01Z","updated_at":"2016-12-07T17:24:02Z","html_url":"https://github.com/nilearn/nilearn/pull/1182#discussion_r91342526","pull_request_url":"https://api.github.com/repos/nilearn/nilearn/pulls/1182","_links":{"self":{"href":"https://api.github.com/repos/nilearn/nilearn/pulls/comments/91342526"},"html":{"href":"https://github.com/nilearn/nilearn/pull/1182#discussion_r91342526"},"pull_request":{"href":"https://api.github.com/repos/nilearn/nilearn/pulls/1182"}}},"pull_request":{"url":"https://api.github.com/repos/nilearn/nilearn/pulls/1182","id":78110962,"html_url":"https://github.com/nilearn/nilearn/pull/1182","diff_url":"https://github.com/nilearn/nilearn/pull/1182.diff","patch_url":"https://github.com/nilearn/nilearn/pull/1182.patch","issue_url":"https://api.github.com/repos/nilearn/nilearn/issues/1182","number":1182,"state":"open","locked":false,"title":"Add a Neurovault fetcher.","user":{"login":"jeromedockes","id":9196501,"avatar_url":"https://avatars.githubusercontent.com/u/9196501?v=3","gravatar_id":"","url":"https://api.github.com/users/jeromedockes","html_url":"https://github.com/jeromedockes","followers_url":"https://api.github.com/users/jeromedockes/followers","following_url":"https://api.github.com/users/jeromedockes/following{/other_user}","gists_url":"https://api.github.com/users/jeromedockes/gists{/gist_id}","starred_url":"https://api.github.com/users/jeromedockes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jeromedockes/subscriptions","organizations_url":"https://api.github.com/users/jeromedockes/orgs","repos_url":"https://api.github.com/users/jeromedockes/repos","events_url":"https://api.github.com/users/jeromedockes/events{/privacy}","received_events_url":"https://api.github.com/users/jeromedockes/received_events","type":"User","site_admin":false},"body":"This is based on PR #832 opened by @bcipolli in answer to issue\n#640. The contribution is to add a fetcher for downloading selected\n\nimages from http://neurovault.org/. I have tried to address some of\nthe remarks that were made in the discussion about #832. I have\nincluded the examples `plot_ica_neurovault.py` and\n`plot_neurovault_meta_analysis.py` from the previous PR; they remain\nalmost identical.\n\nThe interface to the fetcher is similar to that proposed in #832. I\nhave kept the possibility to filter collections and images either with\na function or with a dictionary of `{field: desired_value}` pairs (or\nboth), but they are now separate arguments called respectively\n`collection_filter` (`image_filter` for images) and `collection_terms`\n(`image_terms` for images). Also, now the filters passed in a\ndictionary are only inserted in the query URL if they are actually\navailable on the server (which is the case for the collection owner,\nthe collection DOI, and the collection name); otherwise they are\napplied to the metadata once it has been downloaded.\n\nFor users who want a specific set of image or collection ids,\ndownloading all the Neurovault metadata and filtering on the ids is\ninefficient; so they can use the `image_ids` or `collection_ids`\nparameters to pass the lists of ids they want to download; in this\ncase any filter is ignored and the server is queried directly for the\nrequired image and collection ids. Note: this is also done under the\nhood if the collection id is used as a filter term - either by\nspecifying the collection 'id' field or the image 'collection_id'\nfield - except that in this case the other filters are still applied.\n\nI have included a `ResultFilter` class and a bunch of special values\nsuch as `NotNull` which can be used to specify filters more easily. In\nsome neurovault metadata jsons, some values are strings such as \"\",\n\"null\", \"None\"... instead of actual `null`; these (more precisely\nstrings that match `($|n/?a$|none|null)` in a case-insensitive way)\nare replaced by a true null value (`null` in the json, converted to\n`None` when loaded in a python dict) upon download so that comparing\nto `None` or testing for truth should yield the expected results.  In\nparticular `dict.get(field)` will give the same value wether the\noriginal value was `null`, \"null\", ... , or plain missing.\n\nThis should make using `fetch_neurovault` somewhat easier. However,\nfor users who are interested in a large subset of the neurovault data\nand are not too short on disk space, I would recommend using only very\nsimple filters (e.g. the defaults) when calling `fetch_neurovault` to\ndownload (almost all) the data, and, once it is on disk, only access\nit through `read_sql_query`, `local_database_connection` or\n`local_database_cursor`. The metadata is stored in an sqlite database,\nso instead of having to read the docstring for `fetch_neurovault`,\nwriting their own filters, etc., most users will probably prefer to\ndownload it all and then simply use SQL syntax to select the subset\nthey're interested in. `read_sql_query` queries the database and\nreturns the result as an `OrderedDict` of columns (the default) or as\na list of rows. `local_database_connection` gives a connection to the\nsqlite file, so that pandas users can load e.g., all the images'\nmetadata by typing:\n\n```\nimages = pandas.read_sql_query(\n     \"SELECT * FROM images\", neurovault.local_database_connection())\n```\n\nOf course if they prefer manipulating sqlite3 objects directly they\ncan use the connection given by `local_database_connection` or the\ncursor given by `local_database_cursor`.\n\n@bcipolli, @chrisfilo, @GaelVaroquaux, or anyone else, please let me\nknow what modifications need to be made! In particular:\n- What should be the default filters? The current behaviour is to\n  exclude:\n  - Collections from a list of known bad collections (found in PR #832,  with one addition).\n  - Empty collections.\n  - Images from a list of known bad images (found in PR #832).\n  - Images that are not in MNI space.\n  - Images for which the metadata field 'is_valid' is cleared.\n  - Images for which the metadata field 'is_thresholded' is set.\n  - Images for which 'map_type' is 'ROI/mask', 'anatomical' or 'parcellation'\n  - Images for which 'image_type' is 'atlas'\n- Should the fetcher, by default, download all the images matching the\n  filters, or only a limited number (the current behaviour)?\n","created_at":"2016-07-20T08:58:52Z","updated_at":"2016-12-07T17:24:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":"480c7e611d5cb4932e848ce7fdc22f9bc231e680","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/nilearn/nilearn/pulls/1182/commits","review_comments_url":"https://api.github.com/repos/nilearn/nilearn/pulls/1182/comments","review_comment_url":"https://api.github.com/repos/nilearn/nilearn/pulls/comments{/number}","comments_url":"https://api.github.com/repos/nilearn/nilearn/issues/1182/comments","statuses_url":"https://api.github.com/repos/nilearn/nilearn/statuses/78155063966a21a9337a77121b986bcba2222455","head":{"label":"jeromedockes:neurovault_fetcher","ref":"neurovault_fetcher","sha":"78155063966a21a9337a77121b986bcba2222455","user":{"login":"jeromedockes","id":9196501,"avatar_url":"https://avatars.githubusercontent.com/u/9196501?v=3","gravatar_id":"","url":"https://api.github.com/users/jeromedockes","html_url":"https://github.com/jeromedockes","followers_url":"https://api.github.com/users/jeromedockes/followers","following_url":"https://api.github.com/users/jeromedockes/following{/other_user}","gists_url":"https://api.github.com/users/jeromedockes/gists{/gist_id}","starred_url":"https://api.github.com/users/jeromedockes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jeromedockes/subscriptions","organizations_url":"https://api.github.com/users/jeromedockes/orgs","repos_url":"https://api.github.com/users/jeromedockes/repos","events_url":"https://api.github.com/users/jeromedockes/events{/privacy}","received_events_url":"https://api.github.com/users/jeromedockes/received_events","type":"User","site_admin":false},"repo":{"id":55776026,"name":"nilearn","full_name":"jeromedockes/nilearn","owner":{"login":"jeromedockes","id":9196501,"avatar_url":"https://avatars.githubusercontent.com/u/9196501?v=3","gravatar_id":"","url":"https://api.github.com/users/jeromedockes","html_url":"https://github.com/jeromedockes","followers_url":"https://api.github.com/users/jeromedockes/followers","following_url":"https://api.github.com/users/jeromedockes/following{/other_user}","gists_url":"https://api.github.com/users/jeromedockes/gists{/gist_id}","starred_url":"https://api.github.com/users/jeromedockes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jeromedockes/subscriptions","organizations_url":"https://api.github.com/users/jeromedockes/orgs","repos_url":"https://api.github.com/users/jeromedockes/repos","events_url":"https://api.github.com/users/jeromedockes/events{/privacy}","received_events_url":"https://api.github.com/users/jeromedockes/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jeromedockes/nilearn","description":"Machine learning for NeuroImaging in Python","fork":true,"url":"https://api.github.com/repos/jeromedockes/nilearn","forks_url":"https://api.github.com/repos/jeromedockes/nilearn/forks","keys_url":"https://api.github.com/repos/jeromedockes/nilearn/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jeromedockes/nilearn/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jeromedockes/nilearn/teams","hooks_url":"https://api.github.com/repos/jeromedockes/nilearn/hooks","issue_events_url":"https://api.github.com/repos/jeromedockes/nilearn/issues/events{/number}","events_url":"https://api.github.com/repos/jeromedockes/nilearn/events","assignees_url":"https://api.github.com/repos/jeromedockes/nilearn/assignees{/user}","branches_url":"https://api.github.com/repos/jeromedockes/nilearn/branches{/branch}","tags_url":"https://api.github.com/repos/jeromedockes/nilearn/tags","blobs_url":"https://api.github.com/repos/jeromedockes/nilearn/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jeromedockes/nilearn/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jeromedockes/nilearn/git/refs{/sha}","trees_url":"https://api.github.com/repos/jeromedockes/nilearn/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jeromedockes/nilearn/statuses/{sha}","languages_url":"https://api.github.com/repos/jeromedockes/nilearn/languages","stargazers_url":"https://api.github.com/repos/jeromedockes/nilearn/stargazers","contributors_url":"https://api.github.com/repos/jeromedockes/nilearn/contributors","subscribers_url":"https://api.github.com/repos/jeromedockes/nilearn/subscribers","subscription_url":"https://api.github.com/repos/jeromedockes/nilearn/subscription","commits_url":"https://api.github.com/repos/jeromedockes/nilearn/commits{/sha}","git_commits_url":"https://api.github.com/repos/jeromedockes/nilearn/git/commits{/sha}","comments_url":"https://api.github.com/repos/jeromedockes/nilearn/comments{/number}","issue_comment_url":"https://api.github.com/repos/jeromedockes/nilearn/issues/comments{/number}","contents_url":"https://api.github.com/repos/jeromedockes/nilearn/contents/{+path}","compare_url":"https://api.github.com/repos/jeromedockes/nilearn/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jeromedockes/nilearn/merges","archive_url":"https://api.github.com/repos/jeromedockes/nilearn/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jeromedockes/nilearn/downloads","issues_url":"https://api.github.com/repos/jeromedockes/nilearn/issues{/number}","pulls_url":"https://api.github.com/repos/jeromedockes/nilearn/pulls{/number}","milestones_url":"https://api.github.com/repos/jeromedockes/nilearn/milestones{/number}","notifications_url":"https://api.github.com/repos/jeromedockes/nilearn/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jeromedockes/nilearn/labels{/name}","releases_url":"https://api.github.com/repos/jeromedockes/nilearn/releases{/id}","deployments_url":"https://api.github.com/repos/jeromedockes/nilearn/deployments","created_at":"2016-04-08T12:05:30Z","updated_at":"2016-04-08T12:05:32Z","pushed_at":"2016-11-30T13:19:17Z","git_url":"git://github.com/jeromedockes/nilearn.git","ssh_url":"git@github.com:jeromedockes/nilearn.git","clone_url":"https://github.com/jeromedockes/nilearn.git","svn_url":"https://github.com/jeromedockes/nilearn","homepage":"http://nilearn.github.io","size":10646,"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":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"nilearn:master","ref":"master","sha":"048a26afb880f9c1029e44ad2235e7b062eea85a","user":{"login":"nilearn","id":4646912,"avatar_url":"https://avatars.githubusercontent.com/u/4646912?v=3","gravatar_id":"","url":"https://api.github.com/users/nilearn","html_url":"https://github.com/nilearn","followers_url":"https://api.github.com/users/nilearn/followers","following_url":"https://api.github.com/users/nilearn/following{/other_user}","gists_url":"https://api.github.com/users/nilearn/gists{/gist_id}","starred_url":"https://api.github.com/users/nilearn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nilearn/subscriptions","organizations_url":"https://api.github.com/users/nilearn/orgs","repos_url":"https://api.github.com/users/nilearn/repos","events_url":"https://api.github.com/users/nilearn/events{/privacy}","received_events_url":"https://api.github.com/users/nilearn/received_events","type":"Organization","site_admin":false},"repo":{"id":1235740,"name":"nilearn","full_name":"nilearn/nilearn","owner":{"login":"nilearn","id":4646912,"avatar_url":"https://avatars.githubusercontent.com/u/4646912?v=3","gravatar_id":"","url":"https://api.github.com/users/nilearn","html_url":"https://github.com/nilearn","followers_url":"https://api.github.com/users/nilearn/followers","following_url":"https://api.github.com/users/nilearn/following{/other_user}","gists_url":"https://api.github.com/users/nilearn/gists{/gist_id}","starred_url":"https://api.github.com/users/nilearn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nilearn/subscriptions","organizations_url":"https://api.github.com/users/nilearn/orgs","repos_url":"https://api.github.com/users/nilearn/repos","events_url":"https://api.github.com/users/nilearn/events{/privacy}","received_events_url":"https://api.github.com/users/nilearn/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/nilearn/nilearn","description":"Machine learning for NeuroImaging in Python","fork":false,"url":"https://api.github.com/repos/nilearn/nilearn","forks_url":"https://api.github.com/repos/nilearn/nilearn/forks","keys_url":"https://api.github.com/repos/nilearn/nilearn/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nilearn/nilearn/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nilearn/nilearn/teams","hooks_url":"https://api.github.com/repos/nilearn/nilearn/hooks","issue_events_url":"https://api.github.com/repos/nilearn/nilearn/issues/events{/number}","events_url":"https://api.github.com/repos/nilearn/nilearn/events","assignees_url":"https://api.github.com/repos/nilearn/nilearn/assignees{/user}","branches_url":"https://api.github.com/repos/nilearn/nilearn/branches{/branch}","tags_url":"https://api.github.com/repos/nilearn/nilearn/tags","blobs_url":"https://api.github.com/repos/nilearn/nilearn/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nilearn/nilearn/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nilearn/nilearn/git/refs{/sha}","trees_url":"https://api.github.com/repos/nilearn/nilearn/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nilearn/nilearn/statuses/{sha}","languages_url":"https://api.github.com/repos/nilearn/nilearn/languages","stargazers_url":"https://api.github.com/repos/nilearn/nilearn/stargazers","contributors_url":"https://api.github.com/repos/nilearn/nilearn/contributors","subscribers_url":"https://api.github.com/repos/nilearn/nilearn/subscribers","subscription_url":"https://api.github.com/repos/nilearn/nilearn/subscription","commits_url":"https://api.github.com/repos/nilearn/nilearn/commits{/sha}","git_commits_url":"https://api.github.com/repos/nilearn/nilearn/git/commits{/sha}","comments_url":"https://api.github.com/repos/nilearn/nilearn/comments{/number}","issue_comment_url":"https://api.github.com/repos/nilearn/nilearn/issues/comments{/number}","contents_url":"https://api.github.com/repos/nilearn/nilearn/contents/{+path}","compare_url":"https://api.github.com/repos/nilearn/nilearn/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nilearn/nilearn/merges","archive_url":"https://api.github.com/repos/nilearn/nilearn/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nilearn/nilearn/downloads","issues_url":"https://api.github.com/repos/nilearn/nilearn/issues{/number}","pulls_url":"https://api.github.com/repos/nilearn/nilearn/pulls{/number}","milestones_url":"https://api.github.com/repos/nilearn/nilearn/milestones{/number}","notifications_url":"https://api.github.com/repos/nilearn/nilearn/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nilearn/nilearn/labels{/name}","releases_url":"https://api.github.com/repos/nilearn/nilearn/releases{/id}","deployments_url":"https://api.github.com/repos/nilearn/nilearn/deployments","created_at":"2011-01-09T19:02:23Z","updated_at":"2016-12-07T14:41:39Z","pushed_at":"2016-12-07T11:57:53Z","git_url":"git://github.com/nilearn/nilearn.git","ssh_url":"git@github.com:nilearn/nilearn.git","clone_url":"https://github.com/nilearn/nilearn.git","svn_url":"https://github.com/nilearn/nilearn","homepage":"http://nilearn.github.io","size":13958,"stargazers_count":222,"watchers_count":222,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":154,"mirror_url":null,"open_issues_count":215,"forks":154,"open_issues":215,"watchers":222,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/nilearn/nilearn/pulls/1182"},"html":{"href":"https://github.com/nilearn/nilearn/pull/1182"},"issue":{"href":"https://api.github.com/repos/nilearn/nilearn/issues/1182"},"comments":{"href":"https://api.github.com/repos/nilearn/nilearn/issues/1182/comments"},"review_comments":{"href":"https://api.github.com/repos/nilearn/nilearn/pulls/1182/comments"},"review_comment":{"href":"https://api.github.com/repos/nilearn/nilearn/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/nilearn/nilearn/pulls/1182/commits"},"statuses":{"href":"https://api.github.com/repos/nilearn/nilearn/statuses/78155063966a21a9337a77121b986bcba2222455"}}}}
{"actor":{"display_login":"GaelVaroquaux"}}