Repository: nilearn/nilearn

Issue:
Add Juelich atlas
I just noticed that fsl comes installed with a hell lot of interesting atlases: HarvardOxford, Juelich, JHU, MNI, etc.

Should we have the possibility to fetching this (at least locally when present) ? For example, I find the Juelich atlas really interesting.

![inversecovariance](https://cloud.githubusercontent.com/assets/634068/16281427/b09127d4-38c4-11e6-9545-0dade0a10e0c.png)



Discussion:
+1.

Do you want to do a PR?

ok, no pb.

@achamma723 I think you should take a look at #1146 if you're interested in working on this. 
@KamalakerDadi Hello, can you please tell me how did you manage to add a new dataset (Desterieux) to the nitrc site

Resolving patch:
diff --git a/examples/01_plotting/plot_atlas.py b/examples/01_plotting/plot_atlas.py
--- a/examples/01_plotting/plot_atlas.py
+++ b/examples/01_plotting/plot_atlas.py
@@ -2,7 +2,7 @@
 Basic Atlas plotting
 =======================
 
-Plot the regions of a reference atlas (here the Harvard-Oxford atlas).
+Plot the regions of a reference atlas (Harvard-Oxford and Juelich atlases).
 """
 
 ##########################################################################
@@ -11,10 +11,14 @@
 
 from nilearn import datasets
 
-dataset = datasets.fetch_atlas_harvard_oxford('cort-maxprob-thr25-2mm')
-atlas_filename = dataset.maps
+dataset_ho = datasets.fetch_atlas_harvard_oxford('cort-maxprob-thr25-2mm')
+dataset_ju = datasets.fetch_atlas_juelich('maxprob-thr0-1mm')
 
-print('Atlas ROIs are located at: %s' % atlas_filename)
+atlas_ho_filename = dataset_ho.filename
+atlas_ju_filename = dataset_ju.filename
+
+print('Atlas ROIs are located at: %s' % atlas_ho_filename)
+print('Atlas ROIs are located at: %s' % atlas_ju_filename)
 
 ###########################################################################
 # Visualizing the Harvard-Oxford atlas
@@ -22,11 +26,24 @@
 
 from nilearn import plotting
 
-plotting.plot_roi(atlas_filename, title="Harvard Oxford atlas")
+plotting.plot_roi(atlas_ho_filename, title="Harvard Oxford atlas")
+
+###########################################################################
+# Visualizing the Juelich atlas
+# ------------------------------------
+
+plotting.plot_roi(atlas_ju_filename, title="Juelich atlas")
 
 ###########################################################################
 # Visualizing the Harvard-Oxford atlas with contours
 # --------------------------------------------------
-plotting.plot_roi(atlas_filename, view_type='contours',
+plotting.plot_roi(atlas_ho_filename, view_type='contours',
                   title="Harvard Oxford atlas in contours")
 plotting.show()
+
+###########################################################################
+# Visualizing the Juelich atlas with contours
+# --------------------------------------------------
+plotting.plot_roi(atlas_ju_filename, view_type='contours',
+                  title="Juelich atlas in contours")
+plotting.show()
diff --git a/nilearn/datasets/__init__.py b/nilearn/datasets/__init__.py
--- a/nilearn/datasets/__init__.py
+++ b/nilearn/datasets/__init__.py
@@ -29,8 +29,8 @@
                    fetch_fiac_first_level,
                    )
 from .atlas import (fetch_atlas_craddock_2012, fetch_atlas_destrieux_2009,
-                    fetch_atlas_harvard_oxford, fetch_atlas_msdl,
-                    fetch_coords_power_2011,
+                    fetch_atlas_juelich, fetch_atlas_harvard_oxford,
+                    fetch_atlas_msdl, fetch_coords_power_2011,
                     fetch_coords_seitzman_2018,
                     fetch_atlas_smith_2009,
                     fetch_atlas_yeo_2011, fetch_atlas_aal,
@@ -58,7 +58,8 @@
            'fetch_localizer_button_task',
            'fetch_abide_pcp', 'fetch_localizer_calculation_task',
            'fetch_atlas_craddock_2012', 'fetch_atlas_destrieux_2009',
-           'fetch_atlas_harvard_oxford', 'fetch_atlas_msdl',
+           'fetch_atlas_juelich', 'fetch_atlas_harvard_oxford',
+           'fetch_atlas_msdl',
            'fetch_atlas_schaefer_2018',
            'fetch_coords_power_2011',
            'fetch_coords_seitzman_2018',
diff --git a/nilearn/datasets/atlas.py b/nilearn/datasets/atlas.py
--- a/nilearn/datasets/atlas.py
+++ b/nilearn/datasets/atlas.py
@@ -11,11 +11,12 @@
 import nibabel as nb
 import numpy as np
 from numpy.lib import recfunctions
+import re
 from sklearn.utils import Bunch
 
 from .utils import _get_dataset_dir, _fetch_files, _get_dataset_descr
 from .._utils import check_niimg, fill_doc
-from ..image import new_img_like, get_data
+from ..image import new_img_like, get_data, reorder_img
 
 _TALAIRACH_LEVELS = ['hemisphere', 'lobe', 'gyrus', 'tissue', 'ba']
 
@@ -255,13 +256,17 @@ def fetch_atlas_harvard_oxford(atlas_name, data_dir=None,
     ----------
     atlas_name : string
         Name of atlas to load. Can be:
-        cort-maxprob-thr0-1mm,  cort-maxprob-thr0-2mm,
+        cort-maxprob-thr0-1mm, cort-maxprob-thr0-2mm,
         cort-maxprob-thr25-1mm, cort-maxprob-thr25-2mm,
         cort-maxprob-thr50-1mm, cort-maxprob-thr50-2mm,
-        sub-maxprob-thr0-1mm,  sub-maxprob-thr0-2mm,
+        cort-prob-1mm, cort-prob-2mm,
+        cortl-maxprob-thr0-1mm, cortl-maxprob-thr0-2mm,
+        cortl-maxprob-thr25-1mm, cortl-maxprob-thr25-2mm,
+        cortl-maxprob-thr50-1mm, cortl-maxprob-thr50-2mm,
+        cortl-prob-1mm, cortl-prob-2mm,
+        sub-maxprob-thr0-1mm, sub-maxprob-thr0-2mm,
         sub-maxprob-thr25-1mm, sub-maxprob-thr25-2mm,
         sub-maxprob-thr50-1mm, sub-maxprob-thr50-2mm,
-        cort-prob-1mm, cort-prob-2mm,
         sub-prob-1mm, sub-prob-2mm
 
     data_dir : string, optional
@@ -297,85 +302,277 @@ def fetch_atlas_harvard_oxford(atlas_name, data_dir=None,
 
         - "labels": string list, labels of the regions in the atlas.
 
+    See also
+    --------
+    nilearn.datasets.fetch_atlas_juelich
+
+    """
+    atlases = ["cort-maxprob-thr0-1mm", "cort-maxprob-thr0-2mm",
+               "cort-maxprob-thr25-1mm", "cort-maxprob-thr25-2mm",
+               "cort-maxprob-thr50-1mm", "cort-maxprob-thr50-2mm",
+               "cort-prob-1mm", "cort-prob-2mm",
+               "cortl-maxprob-thr0-1mm", "cortl-maxprob-thr0-2mm",
+               "cortl-maxprob-thr25-1mm", "cortl-maxprob-thr25-2mm",
+               "cortl-maxprob-thr50-1mm", "cortl-maxprob-thr50-2mm",
+               "cortl-prob-1mm", "cortl-prob-2mm",
+               "sub-maxprob-thr0-1mm", "sub-maxprob-thr0-2mm",
+               "sub-maxprob-thr25-1mm", "sub-maxprob-thr25-2mm",
+               "sub-maxprob-thr50-1mm", "sub-maxprob-thr50-2mm",
+               "sub-prob-1mm", "sub-prob-2mm"]
+    if atlas_name not in atlases:
+        raise ValueError("Invalid atlas name: {0}. Please choose "
+                         "an atlas among:\n{1}".
+                         format(atlas_name, '\n'.join(atlases)))
+    is_probabilistic = "-prob-" in atlas_name
+    if is_probabilistic and symmetric_split:
+        raise ValueError("Region splitting not supported for probabilistic "
+                         "atlases")
+    atlas_img, names, is_lateralized = _get_atlas_data_and_labels(
+        "HarvardOxford",
+        atlas_name,
+        symmetric_split=symmetric_split,
+        data_dir=data_dir,
+        resume=resume,
+        verbose=verbose)
+    atlas_niimg = check_niimg(atlas_img)
+    if not symmetric_split or is_lateralized:
+        return Bunch(filename=atlas_img, maps=atlas_niimg, labels=names)
+    new_atlas_data, new_names = _compute_symmetric_split("HarvardOxford",
+                                                         atlas_niimg,
+                                                         names)
+    new_atlas_niimg = new_img_like(atlas_niimg,
+                                   new_atlas_data,
+                                   atlas_niimg.affine)
+    return Bunch(filename=atlas_img, maps=new_atlas_niimg, labels=new_names)
+
+
+def fetch_atlas_juelich(atlas_name, data_dir=None,
+                        symmetric_split=False,
+                        resume=True, verbose=1):
+    """Load Juelich parcellations from FSL.
+
+    This function downloads Juelich atlas packaged from FSL 5.0
+    and stores atlases in NILEARN_DATA folder in home directory.
+
+    This function can also load Juelich atlas from your local directory
+    specified by your FSL installed path given in `data_dir` argument.
+    See documentation for details.
+
+    .. versionadded:: 0.8.1
+
+    Parameters
+    ----------
+    atlas_name : string
+        Name of atlas to load. Can be:
+        maxprob-thr0-1mm,  maxprob-thr0-2mm,
+        maxprob-thr25-1mm, maxprob-thr25-2mm,
+        maxprob-thr50-1mm, maxprob-thr50-2mm,
+        prob-1mm,          prob-2mm
+
+    data_dir : string, optional
+        Path of data directory where data will be stored. Optionally,
+        it can also be a FSL installation directory (which is dependent
+        on your installation).
+        Example, if FSL is installed in /usr/share/fsl/ then
+        specifying as '/usr/share/' can get you Juelich atlas
+        from your installed directory. Since we mimic same root directory
+        as FSL to load it easily from your installation.
+
+    symmetric_split : bool, optional
+        If True, lateralized atlases of cort or sub with maxprob will be
+        returned. For subcortical types (sub-maxprob), we split every
+        symmetric region in left and right parts. Effectively doubles the
+        number of regions.
+        NOTE Not implemented for full probabilistic atlas (*-prob-* atlases).
+        Default=False.
+
+    resume : bool, optional
+        Whether to resumed download of a partly-downloaded file.
+        Default=True.
+
+    verbose : int, optional
+        Verbosity level (0 means no message). Default=1.
+
+    Returns
+    -------
+    data : sklearn.datasets.base.Bunch
+        Dictionary-like object, keys are:
+
+        - "maps": nibabel.Nifti1Image, 4D maps if a probabilistic atlas is
+          requested and 3D labels if a maximum probabilistic atlas was
+          requested.
+
+        - "labels": string list, labels of the regions in the atlas.
+
+    See also
+    --------
+    nilearn.datasets.fetch_atlas_harvard_oxford
+
     """
-    atlas_items = ("cort-maxprob-thr0-1mm", "cort-maxprob-thr0-2mm",
-                   "cort-maxprob-thr25-1mm", "cort-maxprob-thr25-2mm",
-                   "cort-maxprob-thr50-1mm", "cort-maxprob-thr50-2mm",
-                   "sub-maxprob-thr0-1mm", "sub-maxprob-thr0-2mm",
-                   "sub-maxprob-thr25-1mm", "sub-maxprob-thr25-2mm",
-                   "sub-maxprob-thr50-1mm", "sub-maxprob-thr50-2mm",
-                   "cort-prob-1mm", "cort-prob-2mm",
-                   "sub-prob-1mm", "sub-prob-2mm")
-    if atlas_name not in atlas_items:
-        raise ValueError("Invalid atlas name: {0}. Please chose an atlas "
-                         "among:\n{1}".format(
-                             atlas_name, '\n'.join(atlas_items)))
-
-    url = 'http://www.nitrc.org/frs/download.php/9902/HarvardOxford.tgz'
+    atlases = ["maxprob-thr0-1mm", "maxprob-thr0-2mm",
+               "maxprob-thr25-1mm", "maxprob-thr25-2mm",
+               "maxprob-thr50-1mm", "maxprob-thr50-2mm",
+               "prob-1mm", "prob-2mm"]
+    if atlas_name not in atlases:
+        raise ValueError("Invalid atlas name: {0}. Please choose "
+                         "an atlas among:\n{1}".
+                         format(atlas_name, '\n'.join(atlases)))
+    is_probabilistic = atlas_name.startswith("prob-")
+    if is_probabilistic and symmetric_split:
+        raise ValueError("Region splitting not supported for probabilistic "
+                         "atlases")
+    atlas_img, names, _ = _get_atlas_data_and_labels("Juelich",
+                                                     atlas_name,
+                                                     data_dir=data_dir,
+                                                     resume=resume,
+                                                     verbose=verbose)
+    atlas_niimg = check_niimg(atlas_img)
+    atlas_data = get_data(atlas_niimg)
+
+    if is_probabilistic:
+        new_atlas_data, new_names = _merge_probabilistic_maps_juelich(
+            atlas_data, names)
+    elif symmetric_split:
+        new_atlas_data, new_names = _compute_symmetric_split("Juelich",
+                                                             atlas_niimg,
+                                                             names)
+    else:
+        new_atlas_data, new_names = _merge_labels_juelich(atlas_data, names)
+
+    new_atlas_niimg = new_img_like(atlas_niimg,
+                                   new_atlas_data,
+                                   atlas_niimg.affine)
+    return Bunch(filename=atlas_img, maps=new_atlas_niimg,
+                 labels=list(new_names))
+
 
+def _get_atlas_data_and_labels(atlas_source, atlas_name, symmetric_split=False,
+                               data_dir=None, resume=True, verbose=1):
+    """Helper function for both fetch_atlas_juelich and fetch_atlas_harvard_oxford.
+    This function downloads the atlas image and labels.
+    """
+    if atlas_source == "Juelich":
+        url = 'https://www.nitrc.org/frs/download.php/12096/Juelich.tgz'
+    elif atlas_source == "HarvardOxford":
+        url = 'http://www.nitrc.org/frs/download.php/9902/HarvardOxford.tgz'
+    else:
+        raise ValueError("Atlas source {} is not valid.".format(
+            atlas_source))
     # For practical reasons, we mimic the FSL data directory here.
-    dataset_name = 'fsl'
-    data_dir = _get_dataset_dir(dataset_name, data_dir=data_dir,
+    data_dir = _get_dataset_dir('fsl', data_dir=data_dir,
                                 verbose=verbose)
     opts = {'uncompress': True}
     root = os.path.join('data', 'atlases')
 
-    if atlas_name[0] == 'c':
-        if 'cort-maxprob' in atlas_name and symmetric_split:
-            split_name = atlas_name.split('cort')
-            atlas_name = 'cortl' + split_name[1]
+    if atlas_source == 'HarvardOxford':
+        if symmetric_split:
+            atlas_name = atlas_name.replace("cort-max", "cortl-max")
+
+        if atlas_name.startswith("sub-"):
+            label_file = 'HarvardOxford-Subcortical.xml'
+            is_lateralized = False
+        elif atlas_name.startswith("cortl"):
             label_file = 'HarvardOxford-Cortical-Lateralized.xml'
-            lateralized = True
+            is_lateralized = True
         else:
             label_file = 'HarvardOxford-Cortical.xml'
-            lateralized = False
+            is_lateralized = False
     else:
-        label_file = 'HarvardOxford-Subcortical.xml'
-        lateralized = False
+        label_file = "Juelich.xml"
+        is_lateralized = False
     label_file = os.path.join(root, label_file)
-
-    atlas_file = os.path.join(root, 'HarvardOxford',
-                              'HarvardOxford-' + atlas_name + '.nii.gz')
-
+    atlas_file = os.path.join(root, atlas_source,
+                              '{}-{}.nii.gz'.format(atlas_source,
+                                                    atlas_name))
     atlas_img, label_file = _fetch_files(
         data_dir,
-        [(atlas_file, url, opts), (label_file, url, opts)],
+        [(atlas_file, url, opts),
+         (label_file, url, opts)],
         resume=resume, verbose=verbose)
-
+    # Reorder image to have positive affine diagonal
+    atlas_img = reorder_img(atlas_img)
     names = {}
     from xml.etree import ElementTree
     names[0] = 'Background'
-    for label in ElementTree.parse(label_file).findall('.//label'):
-        names[int(label.get('index')) + 1] = label.text
-    names = list(names.values())
-
-    if not symmetric_split:
-        return Bunch(maps=atlas_img, labels=names)
-
-    if atlas_name in ("cort-prob-1mm", "cort-prob-2mm",
-                      "sub-prob-1mm", "sub-prob-2mm"):
-        raise ValueError("Region splitting not supported for probabilistic "
-                         "atlases")
-
-    atlas_img = check_niimg(atlas_img)
-    if lateralized:
-        return Bunch(maps=atlas_img, labels=names)
-
-    atlas = get_data(atlas_img)
-
-    labels = np.unique(atlas)
+    for n, label in enumerate(
+            ElementTree.parse(label_file).findall('.//label')):
+        new_idx = int(label.get('index')) + 1
+        if new_idx in names:
+            raise ValueError(
+                f"Duplicate index {new_idx} for labels "
+                f"'{names[new_idx]}', and '{label.text}'")
+        names[new_idx] = label.text
+    # The label indices should range from 0 to nlabel + 1
+    assert list(names.keys()) == list(range(n + 2))
+    names = [item[1] for item in sorted(names.items())]
+    return atlas_img, names, is_lateralized
+
+
+def _merge_probabilistic_maps_juelich(atlas_data, names):
+    """Helper function for fetch_atlas_juelich.
+    This function handles probabilistic juelich atlases
+    when symmetric_split=False. In this situation, we need
+    to merge labels and maps corresponding to left and right
+    regions.
+    """
+    new_names = np.unique([re.sub(r" (L|R)$", "", name) for name in names])
+    new_name_to_idx = {k: v - 1 for v, k in enumerate(new_names)}
+    new_atlas_data = np.zeros((*atlas_data.shape[:3],
+                               len(new_names) - 1))
+    for i, name in enumerate(names):
+        if name != "Background":
+            new_name = re.sub(r" (L|R)$", "", name)
+            new_atlas_data[..., new_name_to_idx[new_name]] += (
+                atlas_data[..., i - 1])
+    return new_atlas_data, new_names
+
+
+def _merge_labels_juelich(atlas_data, names):
+    """Helper function for fetch_atlas_juelich.
+    This function handles 3D atlases when symmetric_split=False.
+    In this case, we need to merge the labels corresponding to
+    left and right regions.
+    """
+    new_names = np.unique([re.sub(r" (L|R)$", "", name) for name in names])
+    new_names_dict = {k: v for v, k in enumerate(new_names)}
+    new_atlas_data = atlas_data.copy()
+    for label, name in enumerate(names):
+        new_name = re.sub(r" (L|R)$", "", name)
+        new_atlas_data[atlas_data == label] = new_names_dict[new_name]
+    return new_atlas_data, new_names
+
+
+def _compute_symmetric_split(source, atlas_niimg, names):
+    """Helper function for both fetch_atlas_juelich and
+    fetch_atlas_harvard_oxford.
+    This function handles 3D atlases when symmetric_split=True.
+    """
+    # The atlas_niimg should have been passed to
+    # reorder_img such that the affine's diagonal
+    # should be positive. This is important to
+    # correctly split left and right hemispheres.
+    assert atlas_niimg.affine[0, 0] > 0
+    atlas_data = get_data(atlas_niimg)
+    labels = np.unique(atlas_data)
     # Build a mask of both halves of the brain
-    middle_ind = (atlas.shape[0] - 1) // 2
-    # Put zeros on the median plane
-    atlas[middle_ind, ...] = 0
+    middle_ind = (atlas_data.shape[0]) // 2
     # Split every zone crossing the median plane into two parts.
-    left_atlas = atlas.copy()
-    left_atlas[middle_ind:, ...] = 0
-    right_atlas = atlas.copy()
-    right_atlas[:middle_ind, ...] = 0
+    left_atlas = atlas_data.copy()
+    left_atlas[middle_ind:] = 0
+    right_atlas = atlas_data.copy()
+    right_atlas[:middle_ind] = 0
+
+    if source == "Juelich":
+        for idx, name in enumerate(names):
+            if name.endswith('L'):
+                names[idx] = re.sub(r" L$", "", name)
+                names[idx] = "Left " + name
+            if name.endswith('R'):
+                names[idx] = re.sub(r" R$", "", name)
+                names[idx] = "Right " + name
 
     new_label = 0
-    new_atlas = atlas.copy()
+    new_atlas = atlas_data.copy()
     # Assumes that the background label is zero.
     new_names = [names[0]]
     for label, name in zip(labels[1:], names[1:]):
@@ -383,19 +580,17 @@ def fetch_atlas_harvard_oxford(atlas_name, data_dir=None,
         left_elements = (left_atlas == label).sum()
         right_elements = (right_atlas == label).sum()
         n_elements = float(left_elements + right_elements)
-        if (left_elements / n_elements < 0.05 or
-                right_elements / n_elements < 0.05):
-            new_atlas[atlas == label] = new_label
+        if (left_elements / n_elements < 0.05
+                or right_elements / n_elements < 0.05):
+            new_atlas[atlas_data == label] = new_label
             new_names.append(name)
             continue
-        new_atlas[right_atlas == label] = new_label
-        new_names.append(name + ', left part')
-        new_label += 1
         new_atlas[left_atlas == label] = new_label
-        new_names.append(name + ', right part')
-
-    atlas_img = new_img_like(atlas_img, new_atlas, atlas_img.affine)
-    return Bunch(maps=atlas_img, labels=new_names)
+        new_names.append('Left ' + name)
+        new_label += 1
+        new_atlas[right_atlas == label] = new_label
+        new_names.append('Right ' + name)
+    return new_atlas, new_names
 
 
 @fill_doc


Test patch:
diff --git a/nilearn/datasets/tests/test_atlas.py b/nilearn/datasets/tests/test_atlas.py
--- a/nilearn/datasets/tests/test_atlas.py
+++ b/nilearn/datasets/tests/test_atlas.py
@@ -97,11 +97,11 @@ def test_downloader(tmp_path, request_mocker):
 
     # To test this feature, we do as follow:
     # - create the data dir with a file that has a specific content
-    # - try to download the dataset but make it fail on purpose (by requesting a
-    #   file that is not in the archive)
+    # - try to download the dataset but make it fail
+    #   on purpose (by requesting a file that is not in the archive)
     # - check that the previously created file is untouched :
-    #   - if sandboxing is faulty, the file would be replaced by the file of the
-    #     archive
+    #   - if sandboxing is faulty, the file would be replaced
+    #     by the file of the archive
     #   - if sandboxing works, the file must be untouched.
 
     local_archive = Path(
@@ -143,104 +143,116 @@ def test_downloader(tmp_path, request_mocker):
     assert stuff == ''
 
 
-def test_fail_fetch_atlas_harvard_oxford(tmp_path, request_mocker):
+def test_fetch_atlas_source(tmp_path, request_mocker):
+
+    # specify non-existing atlas source
+    with pytest.raises(ValueError, match='Atlas source'):
+        atlas._get_atlas_data_and_labels('new_source', 'not_inside')
+
+
+def _write_to_xml(ho_dir, filename, is_symm):
+    with open(os.path.join(ho_dir, filename + '.xml'), 'w') as dm:
+        if(not is_symm):
+            dm.write("<?xml version='1.0' encoding='us-ascii'?>\n"
+                     "<data>\n"
+                     '<label index="0" x="48" y="94" z="35">R1</label>\n'
+                     '<label index="1" x="25" y="70" z="32">R2</label>\n'
+                     '<label index="2" x="33" y="73" z="63">R3</label>\n'
+                     "</data>")
+        else:
+            dm.write("<?xml version='1.0' encoding='us-ascii'?>\n"
+                     "<data>\n"
+                     '<label index="0" x="63" y="86" z="49">Left R1</label>\n'
+                     '<label index="1" x="21" y="86" z="33">Right R1</label>\n'
+                     '<label index="2" x="64" y="69" z="32">Left R2</label>\n'
+                     '<label index="3" x="26" y="70" z="32">Right R2</label>\n'
+                     '<label index="4" x="47" y="75" z="66">Left R3</label>\n'
+                     '<label index="5" x="43" y="80" z="61">Right R3</label>\n'
+                     "</data>")
+        dm.close()
+
+
+def _test_result_xml(res, is_symm):
+    if not is_symm:
+        assert isinstance(res.maps, nibabel.Nifti1Image)
+        assert isinstance(res.labels, list)
+        assert len(res.labels) == 4
+        assert res.labels[0] == "Background"
+        assert res.labels[1] == "R1"
+        assert res.labels[2] == "R2"
+        assert res.labels[3] == "R3"
+    else:
+        assert isinstance(res.maps, nibabel.Nifti1Image)
+        assert isinstance(res.labels, list)
+        assert len(res.labels) == 7
+        assert res.labels[0] == "Background"
+        assert res.labels[1] == "Left R1"
+        assert res.labels[2] == "Right R1"
+        assert res.labels[3] == "Left R2"
+        assert res.labels[4] == "Right R2"
+        assert res.labels[5] == "Left R3"
+        assert res.labels[6] == "Right R3"
+
+
+@pytest.fixture
+def fsl_fetcher(name):
+    if name == "Juelich":
+        return atlas.fetch_atlas_juelich
+    return atlas.fetch_atlas_harvard_oxford
+
+
+@pytest.mark.parametrize('name,prob',
+                         [("HarvardOxford", "cortl-prob-1mm"),
+                          ("Juelich", "prob-1mm")])
+def test_fetch_atlas_fsl_errors(name, prob, fsl_fetcher,
+                                tmp_path, request_mocker):
     # specify non-existing atlas item
     with pytest.raises(ValueError, match='Invalid atlas name'):
-        atlas.fetch_atlas_harvard_oxford('not_inside')
+        fsl_fetcher('not_inside')
+    # Choose a probabilistic atlas with symmetric split
+    with pytest.raises(ValueError, match='Region splitting'):
+        fsl_fetcher(prob, data_dir=str(tmp_path), symmetric_split=True)
 
-    # specify existing atlas item
-    target_atlas = 'cort-maxprob-thr0-1mm'
-    target_atlas_fname = 'HarvardOxford-' + target_atlas + '.nii.gz'
-    ho_dir = str(tmp_path / 'fsl' / 'data' / 'atlases')
-    os.makedirs(ho_dir)
-    nifti_dir = os.path.join(ho_dir, 'HarvardOxford')
-    os.makedirs(nifti_dir)
-
-    target_atlas_nii = os.path.join(nifti_dir, target_atlas_fname)
 
+@pytest.fixture
+def atlas_data():
     # Create false atlas
     atlas_data = np.zeros((10, 10, 10), dtype=int)
-
     # Create an interhemispheric map
     atlas_data[:, :2, :] = 1
-
     # Create a left map
-    atlas_data[:5, 3:5, :] = 2
-
-    # Create a right map, with one voxel on the left side
     atlas_data[5:, 7:9, :] = 3
+    atlas_data[5:, 3, :] = 2
+    # Create a right map, with one voxel on the left side
+    atlas_data[:5:, 3:5, :] = 2
+    atlas_data[:5, 8, :] = 3
     atlas_data[4, 7, 0] = 3
-
+    return atlas_data
+
+
+@pytest.mark.parametrize('name,label_fname,fname,is_symm,split',
+                         [("HarvardOxford", "-Cortical",
+                           "cort-prob-1mm", False, False),
+                          ("HarvardOxford", "-Subcortical",
+                           "sub-maxprob-thr0-1mm", False, True),
+                          ("HarvardOxford", "-Cortical-Lateralized",
+                           "cortl-maxprob-thr0-1mm", True, True),
+                          ("Juelich", "", "prob-1mm", False, False),
+                          ("Juelich", "", "maxprob-thr0-1mm", False, False),
+                          ("Juelich", "", "maxprob-thr0-1mm", False, True)])
+def test_fetch_atlas_fsl(name, label_fname, fname, is_symm, split,
+                         atlas_data, fsl_fetcher, tmp_path, request_mocker):
+    ho_dir = str(tmp_path / 'fsl' / 'data' / 'atlases')
+    os.makedirs(ho_dir)
+    nifti_dir = os.path.join(ho_dir, name)
+    os.makedirs(nifti_dir)
+    _write_to_xml(ho_dir, f"{name}{label_fname}", is_symm=is_symm)
+    target_atlas_fname = f'{name}-{fname}.nii.gz'
+    target_atlas_nii = os.path.join(nifti_dir, target_atlas_fname)
     nibabel.Nifti1Image(atlas_data, np.eye(4) * 3).to_filename(
         target_atlas_nii)
-
-    dummy = open(os.path.join(ho_dir, 'HarvardOxford-Cortical.xml'), 'w')
-    dummy.write("<?xml version='1.0' encoding='us-ascii'?>\n"
-                "<data>\n"
-                '<label index="0" x="48" y="94" z="35">R1</label>\n'
-                '<label index="1" x="25" y="70" z="32">R2</label>\n'
-                '<label index="2" x="33" y="73" z="63">R3</label>\n'
-                "</data>")
-    dummy.close()
-
-    # when symmetric_split=False (by default), then atlas fetcher should
-    # have maps as string and n_labels=4 with background. Since, we relay on xml
-    # file to retrieve labels.
-    ho_wo_symm = atlas.fetch_atlas_harvard_oxford(target_atlas,
-                                                  data_dir=tmp_path)
-    assert isinstance(ho_wo_symm.maps, str)
-    assert isinstance(ho_wo_symm.labels, list)
-    assert ho_wo_symm.labels[0] == "Background"
-    assert ho_wo_symm.labels[1] == "R1"
-    assert ho_wo_symm.labels[2] == "R2"
-    assert ho_wo_symm.labels[3] == "R3"
-
-    # This section tests with lateralized version. In other words,
-    # symmetric_split=True
-
-    # Dummy xml file for lateralized control of cortical atlas images
-    # shipped with FSL 5.0. Atlases are already lateralized in this version
-    # for cortical type atlases denoted with maxprob but not full prob and but
-    # not also with subcortical.
-
-    # So, we test the fetcher with symmetric_split=True by creating a new
-    # dummy local file and fetch them and test the output variables
-    # accordingly.
-    dummy2 = open(os.path.join(ho_dir, 'HarvardOxford-Cortical-Lateralized.xml'), 'w')
-    dummy2.write("<?xml version='1.0' encoding='us-ascii'?>\n"
-                 "<data>\n"
-                 '<label index="0" x="63" y="86" z="49">Left R1</label>\n'
-                 '<label index="1" x="21" y="86" z="33">Right R1</label>\n'
-                 '<label index="2" x="64" y="69" z="32">Left R2</label>\n'
-                 '<label index="3" x="26" y="70" z="32">Right R2</label>\n'
-                 '<label index="4" x="47" y="75" z="66">Left R3</label>\n'
-                 '<label index="5" x="43" y="80" z="61">Right R3</label>\n'
-                 "</data>")
-    dummy2.close()
-
-    # Here, with symmetric_split=True, atlas maps are returned as nibabel Nifti
-    # image but not string. Now, with symmetric split number of labels should be
-    # more than without split and contain Left and Right tags in the labels.
-
-    # Create dummy image files too with cortl specified for symmetric split.
-    split_atlas_fname = 'HarvardOxford-' + 'cortl-maxprob-thr0-1mm' + '.nii.gz'
-    nifti_target_split = os.path.join(nifti_dir, split_atlas_fname)
-    nibabel.Nifti1Image(atlas_data, np.eye(4) * 3).to_filename(
-        nifti_target_split)
-    ho = atlas.fetch_atlas_harvard_oxford(target_atlas,
-                                          data_dir=tmp_path,
-                                          symmetric_split=True)
-
-    assert isinstance(ho.maps, nibabel.Nifti1Image)
-    assert isinstance(ho.labels, list)
-    assert len(ho.labels) == 7
-    assert ho.labels[0] == "Background"
-    assert ho.labels[1] == "Left R1"
-    assert ho.labels[2] == "Right R1"
-    assert ho.labels[3] == "Left R2"
-    assert ho.labels[4] == "Right R2"
-    assert ho.labels[5] == "Left R3"
-    assert ho.labels[6] == "Right R3"
+    ho_wo = fsl_fetcher(fname, data_dir=str(tmp_path), symmetric_split=split)
+    _test_result_xml(ho_wo, is_symm=is_symm or split)
 
 
 def test_fetch_atlas_craddock_2012(tmp_path, request_mocker):
@@ -370,17 +382,17 @@ def test_fetch_atlas_yeo_2011(tmp_path, request_mocker):
 
 
 def test_fetch_atlas_difumo(tmp_path, request_mocker):
-    resolutions = [2, 3] # Valid resolution values
-    dimensions = [64, 128, 256, 512, 1024] # Valid dimension values
-    dimension_urls =  ['pqu9r','wjvd5','3vrct','9b76y','34792']
-    url_mapping = {k:v for k,v in zip(dimensions, dimension_urls)}
+    resolutions = [2, 3]  # Valid resolution values
+    dimensions = [64, 128, 256, 512, 1024]  # Valid dimension values
+    dimension_urls = ['pqu9r', 'wjvd5', '3vrct', '9b76y', '34792']
+    url_mapping = {k: v for k, v in zip(dimensions, dimension_urls)}
     url_count = 1
 
     for dim in dimensions:
         url_count += 1
         url = "*osf.io/{0}/*".format(url_mapping[dim])
         labels = pd.DataFrame(
-            {"Component":  [_ for _ in range(1,dim+1)],
+            {"Component": [_ for _ in range(1, dim + 1)],
              "Difumo_names": ["" for _ in range(dim)],
              "Yeo_networks7": ["" for _ in range(dim)],
              "Yeo_networks17": ["" for _ in range(dim)],
@@ -514,8 +526,7 @@ def test_fetch_atlas_surf_destrieux(tmp_path, request_mocker, verbose=0):
         nibabel.freesurfer.write_annot(
                 os.path.join(data_dir,
                              '%s.aparc.a2009s.annot' % hemi),
-                np.arange(4), np.zeros((4, 5)), 5 * ['a'],
-                )
+                np.arange(4), np.zeros((4, 5)), 5 * ['a'],)
 
     bunch = atlas.fetch_atlas_surf_destrieux(data_dir=tmp_path, verbose=0)
     # Our mock annots have 4 labels
@@ -623,8 +634,8 @@ def test_fetch_atlas_schaefer_2018(tmp_path, request_mocker):
         assert isinstance(data.labels, np.ndarray)
         assert len(data.labels) == n_rois
         assert data.labels[0].astype(str).startswith("{}Networks".
-                                              format(yeo_networks))
+                                                     format(yeo_networks))
         img = nibabel.load(data.maps)
         assert img.header.get_zooms()[0] == resolution_mm
         assert np.array_equal(np.unique(img.dataobj),
-                                   np.arange(n_rois+1))
+                              np.arange(n_rois + 1))