Fix/bug_53 #123

Merged
fraimondo merged 13 commits from fix/bug_53 into main 2022-11-10 12:46:27 +00:00
18 changed files with 831 additions and 155 deletions

View file

@ -83,6 +83,8 @@ Enhancements
synchon commented 2022-11-07 16:50:33 +00:00 (Migrated from github.com)

Include PR number?

Include PR number?
- Rename "atlas" to "parcellation" (:gh:`116` by `Fede Raimondo`_). - Rename "atlas" to "parcellation" (:gh:`116` by `Fede Raimondo`_).
- Refactor the :class:`junifer.datagrabber.BaseDataGrabber` class to allow for easier subclassing (:gh:`123` by `Fede Raimondo`_).
Bugs Bugs
~~~~ ~~~~
@ -91,6 +93,8 @@ Bugs
- Fix a bug in which AOMIC PIOP2 datagrabber did not use user input to constrain elements based on tasks (:gh:`105` by `Leonard Sasse`_) - Fix a bug in which AOMIC PIOP2 datagrabber did not use user input to constrain elements based on tasks (:gh:`105` by `Leonard Sasse`_)
- Fix a bug in which a datalad dataset could remove a user-cloned dataset (:gh:`53` by `Fede Raimondo`_)
API changes API changes
~~~~~~~~~~~ ~~~~~~~~~~~

View file

@ -24,15 +24,11 @@ class DataladAOMICID1000(PatternDataladDataGrabber):
The directory where the datalad dataset will be cloned. If None, The directory where the datalad dataset will be cloned. If None,
the datalad dataset will be cloned into a temporary directory the datalad dataset will be cloned into a temporary directory
(default None). (default None).
**kwargs
Keyword arguments passed to superclass.
""" """
def __init__( def __init__(
self, self,
datadir: Union[str, Path, None] = None, datadir: Union[str, Path, None] = None,
**kwargs,
) -> None: ) -> None:
# The types of data # The types of data
types = [ types = [

View file

@ -8,7 +8,7 @@
from itertools import product from itertools import product
from pathlib import Path from pathlib import Path
from typing import Dict, List, Tuple, Union from typing import Dict, List, Union
from junifer.datagrabber import PatternDataladDataGrabber from junifer.datagrabber import PatternDataladDataGrabber
@ -30,16 +30,12 @@ class DataladAOMICPIOP1(PatternDataladDataGrabber):
"gstroop", "workingmemory"} or list of the options, optional "gstroop", "workingmemory"} or list of the options, optional
AOMIC PIOP1 task sessions. If None, all available task sessions are AOMIC PIOP1 task sessions. If None, all available task sessions are
selected (default None). selected (default None).
**kwargs
Keyword arguments passed to superclass.
""" """
def __init__( def __init__(
self, self,
datadir: Union[str, Path, None] = None, datadir: Union[str, Path, None] = None,
tasks: Union[str, List[str], None] = None, tasks: Union[str, List[str], None] = None,
**kwargs,
) -> None: ) -> None:
# The types of data # The types of data
types = [ types = [
@ -122,25 +118,24 @@ class DataladAOMICPIOP1(PatternDataladDataGrabber):
replacements=replacements, replacements=replacements,
) )
def __getitem__(self, element: Tuple[str, str]) -> Dict[str, Path]: def get_item(self, subject: str, task: str) -> Dict[str, Path]:
"""Index one element in the dataset. """Index one element in the dataset.
Parameters Parameters
---------- ----------
element : tuple of str subject : str
The element to be indexed. First element in the tuple is the The subject ID.
subject, second element is the task. task : {"restingstate", "anticipation", "emomatching", "faces", \
"gstroop", "workingmemory"}
The task to get.
Returns Returns
------- -------
out : dict out : dict
Dictionary of paths for each type of data required for the Dictionary of paths for each type of data required for the
specified element. specified element.
""" """
sub, task = element
# depending on task 'acquisition is different'
task_acqs = { task_acqs = {
"anticipation": "seq", "anticipation": "seq",
"emomatching": "seq", "emomatching": "seq",
@ -152,8 +147,7 @@ class DataladAOMICPIOP1(PatternDataladDataGrabber):
acq = task_acqs[task] acq = task_acqs[task]
new_task = f"{task}_acq-{acq}" new_task = f"{task}_acq-{acq}"
out = super().__getitem__((sub, new_task)) out = super().get_item(subject=subject, task=new_task)
out["meta"]["element"] = {"subject": sub, "task": task}
return out return out
def get_elements(self) -> List: def get_elements(self) -> List:

View file

@ -29,16 +29,12 @@ class DataladAOMICPIOP2(PatternDataladDataGrabber):
or list of the options, optional or list of the options, optional
AOMIC PIOP2 task sessions. If None, all available task sessions are AOMIC PIOP2 task sessions. If None, all available task sessions are
selected (default None). selected (default None).
**kwargs
Keyword arguments passed to superclass.
""" """
def __init__( def __init__(
self, self,
datadir: Union[str, Path, None] = None, datadir: Union[str, Path, None] = None,
tasks: Union[str, List[str], None] = None, tasks: Union[str, List[str], None] = None,
**kwargs,
) -> None: ) -> None:
# The types of data # The types of data
types = [ types = [

View file

@ -63,10 +63,7 @@ class BaseDataGrabber(ABC):
Parameters Parameters
---------- ----------
element : str or tuple element : str or tuple
The element to be indexed. If one string is provided, it is The element to be indexed.
assumed to be a tuple with only one item. If a tuple is provided,
each item in the tuple is the value for the replacement string
specified in "replacements".
Returns Returns
------- -------
@ -76,8 +73,14 @@ class BaseDataGrabber(ABC):
""" """
logger.info(f"Getting element {element}") logger.info(f"Getting element {element}")
out = {} if not isinstance(element, tuple):
out["meta"] = {"datagrabber": self.get_meta()} element = (element,)
named_element = dict(zip(self.get_element_keys(), element))
out = self.get_item(**named_element)
out["meta"] = {
"datagrabber": self.get_meta(),
"element": named_element
}
return out return out
def __enter__(self) -> "BaseDataGrabber": def __enter__(self) -> "BaseDataGrabber":
@ -115,18 +118,6 @@ class BaseDataGrabber(ABC):
t_meta[k] = v t_meta[k] = v
return t_meta return t_meta
# TODO: what is the final functionality?
def get_element_keys(self) -> str:
"""Get element keys.
Returns
-------
str
The element keys.
"""
return "element"
@property @property
def datadir(self) -> Path: def datadir(self) -> Path:
"""Get data directory path. """Get data directory path.
@ -139,6 +130,24 @@ class BaseDataGrabber(ABC):
""" """
synchon commented 2022-11-08 06:18:05 +00:00 (Migrated from github.com)

A more explicit description: For each item in the ``element`` tuple passed to ``__getitem__()``, this method returns the corresponding key(s).

A more explicit description: ```For each item in the ``element`` tuple passed to ``__getitem__()``, this method returns the corresponding key(s).```
return self._datadir return self._datadir
@abstractmethod
def get_element_keys(self) -> str:
"""Get element keys.
For each item in the ``element`` tuple passed to ``__getitem__()``,
this method returns the corresponding key(s).
Returns
-------
str
The element keys.
"""
raise_error(
msg="Concrete classes need to implement get_element_keys().",
klass=NotImplementedError,
)
@abstractmethod @abstractmethod
def get_elements(self) -> List: def get_elements(self) -> List:
"""Get elements. """Get elements.
@ -155,3 +164,24 @@ class BaseDataGrabber(ABC):
msg="Concrete classes need to implement get_elements().", msg="Concrete classes need to implement get_elements().",
synchon commented 2022-11-08 05:50:06 +00:00 (Migrated from github.com)

element : dict

`element : dict`
klass=NotImplementedError, klass=NotImplementedError,
) )
@abstractmethod
def get_item(self, **element: Dict) -> Dict[str, Dict]:
"""Get the specified item from the dataset.
Parameters
----------
element : dict
The element to be indexed.
Returns
-------
dict
Dictionary of paths for each type of data required for the
specified element.
"""
raise_error(
msg="Concrete classes need to implement get_item().",
klass=NotImplementedError,
)

View file

@ -10,11 +10,12 @@ from pathlib import Path
from typing import Dict, Optional, Tuple, Union from typing import Dict, Optional, Tuple, Union
import datalad.api as dl import datalad.api as dl
from datalad.support.gitrepo import GitRepo
from ..api.decorators import register_datagrabber from ..api.decorators import register_datagrabber
from ..utils import logger from ..utils import logger
from .base import BaseDataGrabber from .base import BaseDataGrabber
from .utils import raise_error from ..utils import raise_error, warn_with_log
@register_datagrabber @register_datagrabber
@ -81,12 +82,37 @@ class DataladDataGrabber(BaseDataGrabber):
logger.debug(f"\t_rootdir = {rootdir}") logger.debug(f"\t_rootdir = {rootdir}")
synchon commented 2022-11-10 09:01:17 +00:00 (Migrated from github.com)

For consistency: ... dataset ID

For consistency: `... dataset ID`
synchon commented 2022-11-10 09:01:41 +00:00 (Migrated from github.com)

For consistency: ... dataset ID from ...

For consistency: `... dataset ID from ...`
self.uri = uri self.uri = uri
self._rootdir = rootdir self._rootdir = rootdir
# Flag to indicate if the dataset was cloned before and it might be
# dirty
self._dataset_dirty = False
@property @property
def datadir(self) -> Path: def datadir(self) -> Path:
"""Get data directory path.""" """Get data directory path."""
return super().datadir / self._rootdir return super().datadir / self._rootdir
def _get_dataset_id_remote(self) -> str:
"""Get the dataset id from the remote.
Returns
-------
str
The dataset id.
"""
remote_id = None
with tempfile.TemporaryDirectory() as tmpdir:
logger.debug(f"Querying {self.uri} for dataset ID")
repo = GitRepo.clone(
self.uri, path=tmpdir,
clone_options=["-n", "--depth=1"])
repo.checkout(name=".datalad/config", options=["HEAD"])
remote_id = repo.config.get("datalad.dataset.id", None)
synchon commented 2022-11-08 07:16:53 +00:00 (Migrated from github.com)

Maybe raise as error here?

Maybe raise as error here?
synchon commented 2022-11-08 07:18:12 +00:00 (Migrated from github.com)

Maybe use a new variable here instead of re-assigning.

Maybe use a new variable here instead of re-assigning.
fraimondo commented 2022-11-08 09:38:51 +00:00 (Migrated from github.com)

No need, this is just because mypy can't understand that _dataset.repo is not None at this point.

No need, this is just because mypy can't understand that `_dataset.repo` is not None at this point.
synchon commented 2022-11-08 09:42:34 +00:00 (Migrated from github.com)

I'm a bit unsure of adding an assert statement just because of mypy.

I'm a bit unsure of adding an `assert` statement just because of `mypy`.
fraimondo commented 2022-11-08 10:25:26 +00:00 (Migrated from github.com)

well, it's also good to double check

well, it's also good to double check
synchon commented 2022-11-08 12:09:04 +00:00 (Migrated from github.com)

Yeah that's why I thought an error would be better for this case.

Yeah that's why I thought an error would be better for this case.
logger.debug(f"Got remote dataset ID = {remote_id}")
if remote_id is None:
raise_error("Could not get dataset ID from remote")
return remote_id
def _dataset_get(self, out: Dict) -> Dict: def _dataset_get(self, out: Dict) -> Dict:
"""Get the dataset found from the path in `out`. """Get the dataset found from the path in `out`.
synchon commented 2022-11-10 08:57:28 +00:00 (Migrated from github.com)

Isn't there any other way apart from cloning the dataset again? I'm just trying to understand.

Isn't there any other way apart from cloning the dataset again? I'm just trying to understand.
fraimondo commented 2022-11-10 10:12:56 +00:00 (Migrated from github.com)

No. You need to get the .datalad/config file in the remote. The roblem is that the remote could have different protocols (https, ssh, etc). So doing a no-checkout (-n) shallow (--depth=1) clone and then just getting only the required file is the fastest. It's done only one, on install, only if the dataset was already cloned.

No. You need to get the `.datalad/config` file in the remote. The roblem is that the remote could have different protocols (https, ssh, etc). So doing a no-checkout (`-n`) shallow (`--depth=1`) clone and then just getting only the required file is the fastest. It's done only one, on `install`, only if the dataset was already cloned.
synchon commented 2022-11-10 10:15:27 +00:00 (Migrated from github.com)

Okay thanks!

Okay thanks!
@ -98,39 +124,90 @@ class DataladDataGrabber(BaseDataGrabber):
Returns Returns
synchon commented 2022-11-10 08:59:58 +00:00 (Migrated from github.com)

Can a platform-neutral representation be used here for t_path?

Can a platform-neutral representation be used here for `t_path`?
synchon commented 2022-11-10 09:00:04 +00:00 (Migrated from github.com)

Ditto.

Ditto.
synchon commented 2022-11-10 09:00:26 +00:00 (Migrated from github.com)

... different ID.

`... different ID.`
synchon commented 2022-11-10 09:03:12 +00:00 (Migrated from github.com)

def cleanup(self) -> None:

`def cleanup(self) -> None:`
synchon commented 2022-11-10 09:03:49 +00:00 (Migrated from github.com)

Maybe a platform-neutral representation for f here?

Maybe a platform-neutral representation for `f` here?
fraimondo commented 2022-11-10 10:09:33 +00:00 (Migrated from github.com)

What do you mean?

What do you mean?
synchon commented 2022-11-10 10:11:30 +00:00 (Migrated from github.com)

I mean something like: str(t_path.absolute()) or t_path.resolve()

I mean something like: `str(t_path.absolute())` or `t_path.resolve()`
------- -------
dict dict
The modified dictionary with version appended. The modified dictionary with meta updated.
""" """
for _, v in out.items(): to_get = [v["path"] for v in out.values() if "path" in v]
if "path" in v:
logger.debug(f"Getting {v['path']}") if len(to_get) > 0:
# Note, that `self._dataset.get` without an option would get logger.debug(f"Getting {len(to_get)} files using datalad:")
# the content of all files in a (sub-dataset) if `v["path"]` for fname in to_get:
# was to point to a subdataset rather than a file. This may be logger.debug(f"\t: {fname}")
# a source of confusion (+ performance/storage issue) when
# implementing a grabber. dl_out = self._dataset.get(to_get, result_renderer="disabled")
self._dataset.get(v["path"]) if not self._was_cloned:
logger.debug("Get done") # If the dataset was already installed, check that the
# file was actually downloaded to avoid removing a
# file that was already there.
for t_out in dl_out:
t_path = Path(t_out["path"])
if t_out["status"] == "ok":
logger.debug(f"File {t_path} downloaded")
self._got_files.append(t_path)
elif t_out["status"] == "notneeded":
logger.debug(
f"File {t_path} was already present"
)
else:
raise_error(f"File download failed: {t_out}")
logger.debug("Get done")
# append the version of the dataset
out["meta"]["datagrabber"][
"dataset_commit_id"
] = self._dataset.repo.get_hexsha(
self._dataset.repo.get_corresponding_branch()
)
return out return out
def install(self) -> None: def install(self) -> None:
"""Install the datalad dataset into the datadir.""" """Install the datalad dataset into the datadir.
logger.debug(f"Installing dataset {self.uri} to {self._datadir}")
self._dataset: dl.Dataset = dl.clone(self.uri, self._datadir)
logger.debug("Dataset installed")
def remove(self): Raises
"""Remove the datalad dataset from the datadir.""" ------
# This probably wants to use `reckless='kill'` or similar. ValueError
# See issue #53 If the dataset is already installed but with a different ID.
self._dataset.remove(recursive=True) """
isinstalled = dl.Dataset(self._datadir).is_installed()
if isinstalled:
logger.debug("Dataset already installed")
self._got_files = []
self._dataset: dl.Dataset = dl.Dataset(self._datadir)
remote_id = self._get_dataset_id_remote()
if remote_id != self._dataset.id:
raise_error(
"Dataset already installed but with a different "
f"ID: {self._dataset.id} (local) != {remote_id} (remote)"
)
# Check for dirty datasets:
status = self._dataset.status()
if any([x["state"] != "clean" for x in status]):
self._dataset_dirty = True
warn_with_log(
"At least one file is not clean, Junifer will "
"consider this dataset as dirty."
)
else:
logger.debug("Dataset is clean")
else:
logger.debug(f"Installing dataset {self.uri} to {self._datadir}")
self._dataset: dl.Dataset = dl.clone( # type: ignore
self.uri, self._datadir, result_renderer="disabled"
)
logger.debug("Dataset installed")
self._was_cloned = not isinstalled
self._datalad_commit_id = self._dataset.repo.get_hexsha(
self._dataset.repo.get_corresponding_branch()
)
def cleanup(self) -> None:
"""Cleanup the datalad dataset."""
if self._was_cloned:
logger.debug("Removing dataset with reckless='kill'")
self._dataset.remove(reckless="kill", result_renderer="disabled")
else:
logger.debug("Dropping files that were downloaded")
for f in self._got_files:
logger.debug(f"Dropping {f}")
self._dataset.drop(f, result_renderer="disabled")
def __getitem__(self, element: Union[str, Tuple]) -> Dict[str, Path]: def __getitem__(self, element: Union[str, Tuple]) -> Dict[str, Path]:
"""Implement single element indexing in the Datalad database. """Implement single element indexing in the Datalad database.
synchon commented 2022-11-10 08:58:50 +00:00 (Migrated from github.com)

What if t_out["path"] is already a Path?

What if `t_out["path"]` is already a `Path`?
fraimondo commented 2022-11-10 10:09:26 +00:00 (Migrated from github.com)

It's not. It comes from datalad.

It's not. It comes from datalad.
synchon commented 2022-11-10 10:10:45 +00:00 (Migrated from github.com)

I see, okay.

I see, okay.
@ -166,6 +243,24 @@ class DataladDataGrabber(BaseDataGrabber):
def __exit__(self, exc_type, exc_value, exc_traceback): def __exit__(self, exc_type, exc_value, exc_traceback):
"""Implement context exit.""" """Implement context exit."""
logger.debug("Removing dataset") logger.debug("Cleaning up dataset")
self.remove() self.cleanup()
logger.debug("Dataset removed") logger.debug("Dataset state restored")
def get_meta(self) -> Dict:
"""Get metadata.
Returns
-------
dict
The metadata as dictionary.
"""
t_meta = super().get_meta()
t_meta["datalad_commit_id"] = self._datalad_commit_id
t_meta["datalad_id"] = self._dataset.id
# Set a flag to indicate that the dataset was dirty
t_meta["datalad_dirty"] = self._dataset_dirty
return t_meta

View file

@ -2,7 +2,7 @@
from itertools import product from itertools import product
from pathlib import Path from pathlib import Path
from typing import Dict, List, Tuple, Union from typing import Dict, List, Union
from junifer.datagrabber.datalad_base import DataladDataGrabber from junifer.datagrabber.datalad_base import DataladDataGrabber
@ -104,15 +104,20 @@ class HCP1200(PatternDataGrabber):
) )
self.phase_encodings = phase_encodings self.phase_encodings = phase_encodings
def __getitem__(self, element: Tuple[str, str, str]) -> Dict[str, Path]: def get_item(
self, subject: str, task: str, phase_encoding: str
) -> Dict[str, Path]:
"""Index one element in the dataset. """Index one element in the dataset.
Parameters Parameters
---------- ----------
element : triple of str subject : str
The element to be indexed. First element in the tuple is the The subject ID.
subject, second element is the task, third element is the task : {"REST1", "REST2", "SOCIAL", "WM", "RELATIONAL", "EMOTION", \
phase encoding direction. "LANGUAGE", "GAMBLING", "MOTOR"}
The task.
phase_encoding : {"LR", "RL"}
The phase encoding.
Returns Returns
------- -------
@ -121,20 +126,15 @@ class HCP1200(PatternDataGrabber):
specified element. specified element.
""" """
sub, task, phase_encoding = element
# Resting task # Resting task
if "REST" in task: if "REST" in task:
new_task = f"rfMRI_{task}" new_task = f"rfMRI_{task}"
else: else:
new_task = f"tfMRI_{task}" new_task = f"tfMRI_{task}"
out = super().__getitem__((sub, new_task, phase_encoding)) out = super().get_item(
out["meta"]["element"] = { subject=subject, task=new_task, phase_encoding=phase_encoding
"subject": sub, )
"task": task,
"phase_encoding": phase_encoding,
}
return out return out
def get_elements(self) -> List: def get_elements(self) -> List:
@ -173,17 +173,13 @@ class DataladHCP1200(DataladDataGrabber, HCP1200):
phase_encodings : {"LR", "RL"} or list of the options, optional phase_encodings : {"LR", "RL"} or list of the options, optional
HCP phase encoding directions. If None, both will be used HCP phase encoding directions. If None, both will be used
(default None). (default None).
**kwargs
Keyword arguments passed to superclass.
""" """
def __init__( def __init__(
self, self,
datadir: Union[str, Path, None] = None, datadir: Union[str, Path, None] = None,
tasks: Union[str, List[str], None] = None, tasks: Union[str, List[str], None] = None,
phase_encodings: Union[str, List[str], None] = None, phase_encodings: Union[str, List[str], None] = None
**kwargs,
) -> None: ) -> None:
uri = ( uri = (
"https://github.com/datalad-datasets/" "https://github.com/datalad-datasets/"

View file

@ -26,9 +26,16 @@ class MultipleDataGrabber(BaseDataGrabber):
""" """
def __init__(self, datagrabbers: List[BaseDataGrabber], **kwargs) -> None: def __init__(self, datagrabbers: List[BaseDataGrabber], **kwargs) -> None:
# TODO: Check datagrabbers consistency # Check datagrabbers consistency
# - same element keys # 1) same element keys
# - no overlapping types first_keys = datagrabbers[0].get_element_keys()
for dg in datagrabbers[1:]:
if dg.get_element_keys() != first_keys:
raise ValueError("Datagrabbers have different element keys.")
# 2) no overlapping types
types = [x for dg in datagrabbers for x in dg.get_types()]
if len(types) != len(set(types)):
raise ValueError("Datagrabbers have overlapping types.")
self._datagrabbers = datagrabbers self._datagrabbers = datagrabbers
def __getitem__(self, element: Union[str, Tuple]) -> Dict[str, Path]: def __getitem__(self, element: Union[str, Tuple]) -> Dict[str, Path]:
@ -49,12 +56,34 @@ class MultipleDataGrabber(BaseDataGrabber):
specified element. specified element.
""" """
out = {} out = {}
for dg in self._datagrabbers: for dg in self._datagrabbers:
t_out = dg[element] t_out = dg[element]
out.update(t_out) out.update(t_out)
return out return out
def get_item(self, **element: Dict) -> Dict[str, Dict]:
"""Get item.
Parameters
----------
element : dict
The element to be indexed.
Returns
-------
dict
Dictionary of paths for each type of data required for the
specified element.
Notes
-----
This function is not implemented for this class as it is useless.
"""
raise NotImplementedError(
"get_item() is not useful for this class, hence not implemented.")
synchon commented 2022-11-08 06:47:02 +00:00 (Migrated from github.com)

... -> Dict[str, Path]:?

`... -> Dict[str, Path]:`?
fraimondo commented 2022-11-08 09:40:14 +00:00 (Migrated from github.com)

no, it's the Data Object.

no, it's the Data Object.
def __enter__(self) -> "BaseDataGrabber": def __enter__(self) -> "BaseDataGrabber":
"""Implement context entry.""" """Implement context entry."""
for dg in self._datagrabbers: for dg in self._datagrabbers:
@ -82,6 +111,19 @@ class MultipleDataGrabber(BaseDataGrabber):
elements.intersection_update(s) elements.intersection_update(s)
return list(elements) return list(elements)
def get_element_keys(self) -> List[str]:
"""Get element keys.
For each item in the ``element`` tuple passed to ``__getitem__()``,
this method returns the corresponding key(s).
Returns
-------
list of str
The element keys.
"""
return self._datagrabbers[0].get_element_keys()
def get_types(self) -> List[str]: def get_types(self) -> List[str]:
"""Get types. """Get types.

View file

@ -105,12 +105,12 @@ class PatternDataGrabber(BaseDataGrabber):
glob_pattern = glob_pattern.replace(f"{{{t_r}}}", "*") glob_pattern = glob_pattern.replace(f"{{{t_r}}}", "*")
return re_pattern, glob_pattern, t_replacements return re_pattern, glob_pattern, t_replacements
def _replace_patterns_glob(self, element: Tuple, pattern: str) -> str: def _replace_patterns_glob(self, element: Dict, pattern: str) -> str:
"""Replace patterns with the element so it can be globbed. """Replace patterns with the element so it can be globbed.
synchon commented 2022-11-08 06:01:27 +00:00 (Migrated from github.com)

The type for element in the docstring needs to be updated.

The type for `element` in the docstring needs to be updated.
Parameters Parameters
---------- ----------
element : tuple element : dict
The element to be used in the replacement. The element to be used in the replacement.
pattern : str pattern : str
The pattern to be replaced. The pattern to be replaced.
@ -121,27 +121,39 @@ class PatternDataGrabber(BaseDataGrabber):
The pattern with the element replaced. The pattern with the element replaced.
synchon commented 2022-11-08 06:02:32 +00:00 (Migrated from github.com)

list of str

`list of str`
synchon commented 2022-11-08 06:31:10 +00:00 (Migrated from github.com)

The description can be updated: This method constructs a real path to an item by replacing the ``patterns`` with actual values passed via ``**element`` and then returns the path.

The description can be updated: ```This method constructs a real path to an item by replacing the ``patterns`` with actual values passed via ``**element`` and then returns the path.```
""" """
if len(element) != len(self.replacements): if list(element.keys()) != self.replacements:
raise_error( raise_error(
f"The element length must be {len(self.replacements)}, " f"The element keys must be {self.replacements}, "
f"indicating {self.replacements}." f"element has {list(element.keys())}."
) )
to_replace = dict(zip(self.replacements, element)) return pattern.format(**element)
return pattern.format(**to_replace)
def __getitem__(self, element: Union[str, Tuple]) -> Dict[str, Dict]: def get_element_keys(self) -> List[str]:
"""Get element keys.
For each item in the "element" tuple, this functions returns the
corresponding key, that is, the ``replacements`` of patterns defined
in the constructor.
Returns
-------
list of str
The element keys.
"""
return self.replacements
def get_item(self, **element: Dict) -> Dict[str, Dict]:
"""Implement single element indexing in the database. """Implement single element indexing in the database.
Each occurrence of the strings in "replacements" is replaced by the This method constructs a real path to the requested item's data, by
corresponding item in the element tuple. replacing the ``patterns`` with actual values passed via ``**element``.
Parameters Parameters
---------- ----------
element : str or tuple element : dict
The element to be indexed. If one string is provided, it is The element to be indexed. The keys must be the same as the
synchon commented 2022-11-08 06:06:00 +00:00 (Migrated from github.com)

I think the description here can be rephrased like so: This method returns the ``replacements`` of patterns defined for the datagrabber.

I think the description here can be rephrased like so: `This method returns the ``replacements`` of patterns defined for the datagrabber.`
assumed to be a tuple with only one item. If a tuple is provided, replacements.
each item in the tuple is the value for the replacement string
specified in "replacements".
Returns Returns
------- -------
@ -150,9 +162,7 @@ class PatternDataGrabber(BaseDataGrabber):
specified element. specified element.
""" """
out = super().__getitem__(element) out = {}
if not isinstance(element, tuple):
element = (element,)
for t_type in self.types: for t_type in self.types:
t_pattern = self.patterns[t_type] t_pattern = self.patterns[t_type]
synchon commented 2022-11-08 06:44:17 +00:00 (Migrated from github.com)

... -> Dict[str, Path]:?

`... -> Dict[str, Path]:`?
t_replace = self._replace_patterns_glob(element, t_pattern) t_replace = self._replace_patterns_glob(element, t_pattern)
@ -175,8 +185,6 @@ class PatternDataGrabber(BaseDataGrabber):
f"File {t_out} does not exist" f"File {t_out} does not exist"
) )
out[t_type] = {"path": t_out} out[t_type] = {"path": t_out}
# Meta here is element and types
out["meta"]["element"] = dict(zip(self.replacements, element))
return out return out
def get_elements(self) -> List: def get_elements(self) -> List:

View file

@ -22,18 +22,24 @@ def test_BaseDataGrabber() -> None:
"""Test BaseDataGrabber.""" """Test BaseDataGrabber."""
# Create concrete class. # Create concrete class.
class MyDataGrabber(BaseDataGrabber): class MyDataGrabber(BaseDataGrabber):
def __getitem__(self, element): def get_item(self, subject):
return super().__getitem__(element) return {}
def get_elements(self): def get_elements(self):
return super().get_elements() return super().get_elements()
def get_element_keys(self):
return ["subject"]
dg = MyDataGrabber(datadir="/tmp", types=["func"]) dg = MyDataGrabber(datadir="/tmp", types=["func"])
elem = dg["elem"] elem = dg["elem"]
assert "meta" in elem assert "meta" in elem
assert "datagrabber" in elem["meta"] assert "datagrabber" in elem["meta"]
assert "class" in elem["meta"]["datagrabber"] assert "class" in elem["meta"]["datagrabber"]
assert MyDataGrabber.__name__ in elem["meta"]["datagrabber"]["class"] assert MyDataGrabber.__name__ in elem["meta"]["datagrabber"]["class"]
assert "element" in elem["meta"]
assert "subject" in elem["meta"]["element"]
assert "elem" in elem["meta"]["element"]["subject"]
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
dg.get_elements() dg.get_elements()
@ -41,3 +47,19 @@ def test_BaseDataGrabber() -> None:
with dg: with dg:
assert dg.datadir == Path("/tmp") assert dg.datadir == Path("/tmp")
assert dg.types == ["func"] assert dg.types == ["func"]
class MyDataGrabber2(BaseDataGrabber):
def get_item(self, subject):
return super().get_item(subject=subject)
def get_elements(self):
return super().get_elements()
def get_element_keys(self):
return super().get_element_keys()
dg = MyDataGrabber2(datadir="/tmp", types=["func"])
with pytest.raises(NotImplementedError):
dg.get_element_keys()
with pytest.raises(NotImplementedError):
dg.get_item(subject=1) # type: ignore

View file

@ -5,18 +5,419 @@
import pytest import pytest
from pathlib import Path
import datalad.api as dl
from junifer.datagrabber.datalad_base import DataladDataGrabber from junifer.datagrabber.datalad_base import DataladDataGrabber
_testing_dataset = {
"example_bids": {
"uri": "https://gin.g-node.org/juaml/datalad-example-bids",
"commit": "522dfb203afcd2cd55799bf347f9b211919a7338",
"id": "fec92475-d9c0-4409-92ba-f041b6a12c40",
},
"example_bids_ses": {
"uri": "https://gin.g-node.org/juaml/datalad-example-bids-ses",
"commit": "3d08d55d1faad4f12ab64ac9497544a0d924d47a",
"id": "c83500d0-532f-45be-baf1-0dab703bdc2a",
},
}
def test_datalad_base_abstractness() -> None: def test_datalad_base_abstractness() -> None:
"""Test datalad base is abstract.""" """Test datalad base is abstract."""
synchon commented 2022-11-08 12:12:38 +00:00 (Migrated from github.com)

Needs Parameters section.

Needs `Parameters` section.
with pytest.raises(TypeError, match=r"abstract"): with pytest.raises(TypeError, match=r"abstract"):
DataladDataGrabber() DataladDataGrabber()
# def test_datalad_base_missing_uri() -> None: @pytest.fixture
# """Test proper check of missing URI in datalad base initialization.""" def concrete_datagrabber() -> DataladDataGrabber:
# with pytest.raises(ValueError, match=r"`uri` must be provided"): """Return a concrete datagrabber class.
# DataladDataGrabber(
# ) Returns
-------
DataladDataGrabber
A concrete datagrabber class.
"""
class MyDataGrabber(DataladDataGrabber): # type: ignore
def __init__(self, datadir, uri):
super().__init__(
datadir=datadir,
rootdir="example_bids",
uri=uri,
types=["T1w", "BOLD"],
)
def get_item(self, subject):
out = {
"T1w": {
"path": self.datadir
/ f"{subject}/anat/{subject}_T1w.nii.gz"
},
"BOLD": {
"path": self.datadir
/ f"{subject}/func/{subject}_task-rest_bold.nii.gz"
},
}
return out
def get_elements(self):
return [f"sub-{i:02d}" for i in range(1, 10)]
def get_element_keys(self):
return ["subject"]
return MyDataGrabber
def test_datalad_install_errors(
tmp_path: Path, concrete_datagrabber: DataladDataGrabber
) -> None:
"""Test datalad base install errors / warnings.
Parameters
----------
tmp_path : pathlib.Path
The path to the test directory.
concrete_datagrabber : DataladDataGrabber
A concrete datagrabber class to use.
"""
# Dataset cloned outside of datagrabber
datadir = tmp_path / "cloned_uri"
uri = _testing_dataset["example_bids"]["uri"]
uri2 = _testing_dataset["example_bids_ses"]["uri"]
# Files are not there
assert datadir.exists() is False
# Clone dataset
dl.clone(uri, datadir) # type: ignore
dg = concrete_datagrabber(datadir=datadir, uri=uri2)
with pytest.raises(ValueError, match=r"different ID"):
with dg:
pass
elem1_t1w = datadir / "example_bids/sub-01/anat/sub-01_T1w.nii.gz"
elem1_t1w.unlink()
with open(elem1_t1w, "w") as f:
f.write("modified!")
dg = concrete_datagrabber(datadir=datadir, uri=uri)
with pytest.warns(RuntimeWarning, match=r"one file is not clean"):
with dg:
pass
def test_datalad_clone_cleanup(
tmp_path: Path, concrete_datagrabber: DataladDataGrabber
) -> None:
"""Test datalad base clone and remove.
Parameters
----------
tmp_path : pathlib.Path
The path to the test directory.
concrete_datagrabber : DataladDataGrabber
A concrete datagrabber class to use.
"""
# Clone whole dataset
datadir = tmp_path / "newclone"
uri = _testing_dataset["example_bids"]["uri"]
elem1_bold = (
datadir / "example_bids/sub-01/func/sub-01_task-rest_bold.nii.gz"
)
elem1_t1w = datadir / "example_bids/sub-01/anat/sub-01_T1w.nii.gz"
assert datadir.exists() is False
assert elem1_bold.is_file() is False
assert elem1_t1w.is_file() is False
with concrete_datagrabber(datadir=datadir, uri=uri) as dg:
assert datadir.exists() is True
assert dg._was_cloned is True
assert elem1_bold.is_file() is False
assert elem1_bold.is_symlink() is True
assert elem1_t1w.is_file() is False
assert elem1_t1w.is_symlink() is True
elem1 = dg["sub-01"]
assert "meta" in elem1
assert "datagrabber" in elem1["meta"]
assert "datalad_dirty" in elem1["meta"]["datagrabber"]
assert elem1["meta"]["datagrabber"]["datalad_dirty"] is False
assert hasattr(dg, "_got_files") is False
assert datadir.exists() is True
assert elem1_bold.is_file() is True
assert elem1_bold.is_symlink() is True
assert elem1_t1w.is_file() is True
assert elem1_t1w.is_symlink() is True
assert datadir.exists() is False
assert len(list(datadir.glob("*"))) == 0
def test_datalad_previously_cloned(
tmp_path: Path, concrete_datagrabber: DataladDataGrabber
) -> None:
"""Test datalad base on cloned dataset.
Parameters
----------
tmp_path : pathlib.Path
The path to the test directory.
concrete_datagrabber : DataladDataGrabber
A concrete datagrabber class to use.
"""
# Dataset cloned outside of datagrabber
datadir = tmp_path / "cloned"
elem1_bold = (
datadir / "example_bids/sub-01/func/sub-01_task-rest_bold.nii.gz"
)
elem1_t1w = datadir / "example_bids/sub-01/anat/sub-01_T1w.nii.gz"
uri = _testing_dataset["example_bids"]["uri"]
commit = _testing_dataset["example_bids"]["commit"]
remote_id = _testing_dataset["example_bids"]["id"]
# Files are not there
assert datadir.exists() is False
assert elem1_bold.exists() is False
assert elem1_t1w.exists() is False
# Clone dataset
dl.clone(uri, datadir, result_renderer="disabled") # type: ignore
# Files are there, but are empty symbolic links
assert datadir.exists() is True
assert elem1_bold.is_symlink() is True
assert elem1_bold.is_file() is False
assert elem1_t1w.is_symlink() is True
assert elem1_t1w.is_file() is False
with concrete_datagrabber(datadir=datadir, uri=uri) as dg:
assert datadir.exists() is True
assert dg._was_cloned is False
elem1 = dg["sub-01"]
assert "meta" in elem1
assert "datagrabber" in elem1["meta"]
assert "datalad_dirty" in elem1["meta"]["datagrabber"]
assert elem1["meta"]["datagrabber"]["datalad_dirty"] is False
assert "datalad_commit_id" in elem1["meta"]["datagrabber"]
assert elem1["meta"]["datagrabber"]["datalad_commit_id"] == commit
assert "datalad_id" in elem1["meta"]["datagrabber"]
assert elem1["meta"]["datagrabber"]["datalad_id"] == remote_id
assert hasattr(dg, "_got_files") is True
# Files are there and symlinks are fixed
assert elem1["BOLD"]["path"].is_file() is True
assert elem1["BOLD"]["path"].is_symlink() is True
assert elem1["T1w"]["path"].is_file() is True
assert elem1["T1w"]["path"].is_symlink() is True
# Datagrabber fetched two files
assert len(dg._got_files) == 2
assert any(x.name == "sub-01_T1w.nii.gz" for x in dg._got_files)
assert any(
x.name == "sub-01_task-rest_bold.nii.gz" for x in dg._got_files
)
assert datadir.exists() is True
assert len(list(datadir.glob("*"))) > 0
def test_datalad_previously_cloned_and_get(
tmp_path: Path, concrete_datagrabber: DataladDataGrabber
) -> None:
"""Test datalad base on cloned dataset with files present.
Parameters
----------
tmp_path : pathlib.Path
The path to the test directory.
concrete_datagrabber : DataladDataGrabber
A concrete datagrabber class to use.
"""
# Dataset cloned outside of datagrabber with some files present
datadir = tmp_path / "cloned_clean"
elem1_bold = (
datadir / "example_bids/sub-01/func/sub-01_task-rest_bold.nii.gz"
)
elem1_t1w = datadir / "example_bids/sub-01/anat/sub-01_T1w.nii.gz"
uri = _testing_dataset["example_bids"]["uri"]
commit = _testing_dataset["example_bids"]["commit"]
remote_id = _testing_dataset["example_bids"]["id"]
# Files are not there
assert datadir.exists() is False
assert elem1_bold.exists() is False
assert elem1_t1w.exists() is False
# Clone dataset
dl.clone(uri, datadir, result_renderer="disabled") # type: ignore
# Files are there, but are empty symbolic links
assert datadir.exists() is True
assert elem1_bold.is_symlink() is True
assert elem1_bold.is_file() is False
assert elem1_t1w.is_symlink() is True
assert elem1_t1w.is_file() is False
dl.get( # type: ignore
elem1_t1w, dataset=datadir, result_renderer="disabled"
)
assert elem1_bold.is_symlink() is True
assert elem1_bold.is_file() is False
assert elem1_t1w.is_symlink() is True
assert elem1_t1w.is_file() is True
with concrete_datagrabber(datadir=datadir, uri=uri) as dg:
assert datadir.exists() is True
assert dg._was_cloned is False
elem1 = dg["sub-01"]
assert "meta" in elem1
assert "datagrabber" in elem1["meta"]
assert "datalad_dirty" in elem1["meta"]["datagrabber"]
assert elem1["meta"]["datagrabber"]["datalad_dirty"] is False
assert "datalad_commit_id" in elem1["meta"]["datagrabber"]
assert elem1["meta"]["datagrabber"]["datalad_commit_id"] == commit
assert "datalad_id" in elem1["meta"]["datagrabber"]
assert elem1["meta"]["datagrabber"]["datalad_id"] == remote_id
assert hasattr(dg, "_got_files") is True
# Files are there and symlinks are fixed
assert elem1["BOLD"]["path"].is_file() is True
assert elem1["BOLD"]["path"].is_symlink() is True
assert elem1["T1w"]["path"].is_file() is True
assert elem1["T1w"]["path"].is_symlink() is True
# Datagrabber fetched two files
assert len(dg._got_files) == 1
assert dg._got_files[0].name == "sub-01_task-rest_bold.nii.gz"
assert datadir.exists() is True
assert len(list(datadir.glob("*"))) > 0
# Same state as before using the datagrabber
assert elem1_bold.is_symlink() is True
assert elem1_bold.is_file() is False
assert elem1_t1w.is_symlink() is True
assert elem1_t1w.is_file() is True
def test_datalad_previously_cloned_and_get_dirty(
tmp_path: Path, concrete_datagrabber: DataladDataGrabber
) -> None:
"""Test datalad base on a dirty cloned dataset.
Parameters
----------
tmp_path : pathlib.Path
The path to the test directory.
concrete_datagrabber : DataladDataGrabber
A concrete datagrabber class to use.
"""
# Dataset cloned outside of datagrabber with some files present and dirty
datadir = tmp_path / "cloned_dirty"
elem1_bold = (
datadir / "example_bids/sub-01/func/sub-01_task-rest_bold.nii.gz"
)
elem1_t1w = datadir / "example_bids/sub-01/anat/sub-01_T1w.nii.gz"
uri = _testing_dataset["example_bids"]["uri"]
commit = _testing_dataset["example_bids"]["commit"]
remote_id = _testing_dataset["example_bids"]["id"]
# Files are not there
assert datadir.exists() is False
assert elem1_bold.exists() is False
assert elem1_t1w.exists() is False
# Clone dataset
dl.clone(uri, datadir, result_renderer="disabled") # type: ignore
# Files are there, but are empty symbolic links
assert datadir.exists() is True
assert elem1_bold.is_symlink() is True
assert elem1_bold.is_file() is False
assert elem1_t1w.is_symlink() is True
assert elem1_t1w.is_file() is False
dl.get( # type: ignore
elem1_t1w, dataset=datadir, result_renderer="disabled"
)
assert elem1_bold.is_symlink() is True
assert elem1_bold.is_file() is False
assert elem1_t1w.is_symlink() is True
assert elem1_t1w.is_file() is True
elem1_t1w.unlink()
with open(elem1_t1w, "w") as f:
f.write("modified!")
with concrete_datagrabber(datadir=datadir, uri=uri) as dg:
assert datadir.exists() is True
assert dg._was_cloned is False
elem1 = dg["sub-01"]
assert "meta" in elem1
assert "datagrabber" in elem1["meta"]
assert "datalad_dirty" in elem1["meta"]["datagrabber"]
assert elem1["meta"]["datagrabber"]["datalad_dirty"] is True
assert "datalad_commit_id" in elem1["meta"]["datagrabber"]
assert elem1["meta"]["datagrabber"]["datalad_commit_id"] == commit
assert "datalad_id" in elem1["meta"]["datagrabber"]
assert elem1["meta"]["datagrabber"]["datalad_id"] == remote_id
assert hasattr(dg, "_got_files") is True
# Files are there and symlinks are fixed
assert elem1["BOLD"]["path"].is_file() is True
assert elem1["BOLD"]["path"].is_symlink() is True
assert elem1["T1w"]["path"].is_file() is True
assert elem1["T1w"]["path"].is_symlink() is False
# Datagrabber fetched two files
assert len(dg._got_files) == 1
assert dg._got_files[0].name == "sub-01_task-rest_bold.nii.gz"
# Now get another subject that has not been modified
with concrete_datagrabber(datadir=datadir, uri=uri) as dg:
assert datadir.exists() is True
assert dg._was_cloned is False
elem2 = dg["sub-02"]
assert "meta" in elem2
assert "datagrabber" in elem2["meta"]
assert "datalad_dirty" in elem2["meta"]["datagrabber"]
# Dataset is still dirty due to subject sub-01
assert elem2["meta"]["datagrabber"]["datalad_dirty"] is True
assert "datalad_commit_id" in elem2["meta"]["datagrabber"]
assert elem2["meta"]["datagrabber"]["datalad_commit_id"] == commit
assert "datalad_id" in elem2["meta"]["datagrabber"]
assert elem2["meta"]["datagrabber"]["datalad_id"] == remote_id
assert hasattr(dg, "_got_files") is True
# Files are there and symlinks are fixed
assert elem2["BOLD"]["path"].is_file() is True
assert elem2["BOLD"]["path"].is_symlink() is True
assert elem2["T1w"]["path"].is_file() is True
assert elem2["T1w"]["path"].is_symlink() is True
# Datagrabber fetched two files
assert len(dg._got_files) == 2
assert any(x.name == "sub-02_T1w.nii.gz" for x in dg._got_files)
assert any(
x.name == "sub-02_task-rest_bold.nii.gz" for x in dg._got_files
)
assert datadir.exists() is True
assert len(list(datadir.glob("*"))) > 0
# Same state as before using the datagrabber
assert elem1_bold.is_symlink() is True
assert elem1_bold.is_file() is False
assert elem1_t1w.is_symlink() is False
assert elem1_t1w.is_file() is True

View file

@ -3,13 +3,15 @@
# Authors: Federico Raimondo <f.raimondo@fz-juelich.de> # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
# License: AGPL # License: AGPL
import pytest
from junifer.datagrabber import MultipleDataGrabber, PatternDataladDataGrabber from junifer.datagrabber import MultipleDataGrabber, PatternDataladDataGrabber
_testing_dataset = { _testing_dataset = {
"example_bids": { "example_bids": {
"uri": "https://gin.g-node.org/juaml/datalad-example-bids", "uri": "https://gin.g-node.org/juaml/datalad-example-bids",
"id": "e2ce149bd723088769a86c72e57eded009258c6b", "id": "522dfb203afcd2cd55799bf347f9b211919a7338",
}, },
"example_bids_ses": { "example_bids_ses": {
"uri": "https://gin.g-node.org/juaml/datalad-example-bids-ses", "uri": "https://gin.g-node.org/juaml/datalad-example-bids-ses",
@ -109,3 +111,60 @@ def test_multiple_no_intersection() -> None:
with dg: with dg:
subs = [x for x in dg] subs = [x for x in dg]
assert set(subs) == set(expected_subs) assert set(subs) == set(expected_subs)
def test_multiple_get_item() -> None:
"""Test a multiple datagrabber get_item error."""
repo_uri1 = _testing_dataset["example_bids"]["uri"]
rootdir = "example_bids_ses"
replacements = ["subject", "session"]
pattern1 = {
"T1w": "{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz",
}
dg1 = PatternDataladDataGrabber(
rootdir=rootdir,
uri=repo_uri1,
types=["T1w"],
patterns=pattern1,
replacements=replacements,
)
dg = MultipleDataGrabber([dg1])
with pytest.raises(NotImplementedError):
dg.get_item(subject="sub-01") # type: ignore
def test_multiple_validation() -> None:
"""Test a multiple datagrabber init validation."""
repo_uri1 = _testing_dataset["example_bids"]["uri"]
repo_uri2 = _testing_dataset["example_bids_ses"]["uri"]
rootdir = "example_bids_ses"
replacement1 = ["subject", "session"]
replacement2 = ["subject"]
pattern1 = {
"T1w": "{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz",
}
pattern2 = {
"bold": "{subject}/func/{subject}_task-rest_bold.nii.gz",
}
dg1 = PatternDataladDataGrabber(
rootdir=rootdir,
uri=repo_uri1,
types=["T1w"],
patterns=pattern1,
replacements=replacement1,
)
dg2 = PatternDataladDataGrabber(
rootdir=rootdir,
uri=repo_uri2,
types=["bold"],
patterns=pattern2,
replacements=replacement2,
)
with pytest.raises(ValueError, match="different element key"):
MultipleDataGrabber([dg1, dg2])
with pytest.raises(ValueError, match="overlapping types"):
MultipleDataGrabber([dg1, dg1])

View file

@ -105,7 +105,7 @@ def test_PatternDataGrabber_errors(tmp_path: Path) -> None:
replacements=["subject", "session"], replacements=["subject", "session"],
) )
with pytest.raises(ValueError, match="element length must be"): with pytest.raises(ValueError, match="element keys must be"):
datagrabber["sub001"] datagrabber["sub001"]
# This should not work, file does not exists # This should not work, file does not exists

View file

@ -15,11 +15,13 @@ from junifer.datagrabber.pattern_datalad import PatternDataladDataGrabber
_testing_dataset = { _testing_dataset = {
"example_bids": { "example_bids": {
"uri": "https://gin.g-node.org/juaml/datalad-example-bids", "uri": "https://gin.g-node.org/juaml/datalad-example-bids",
"id": "e2ce149bd723088769a86c72e57eded009258c6b", "commit": "522dfb203afcd2cd55799bf347f9b211919a7338",
"id": "fec92475-d9c0-4409-92ba-f041b6a12c40",
}, },
"example_bids_ses": { "example_bids_ses": {
"uri": "https://gin.g-node.org/juaml/datalad-example-bids-ses", "uri": "https://gin.g-node.org/juaml/datalad-example-bids-ses",
"id": "3d08d55d1faad4f12ab64ac9497544a0d924d47a", "commit": "3d08d55d1faad4f12ab64ac9497544a0d924d47a",
"id": "c83500d0-532f-45be-baf1-0dab703bdc2a",
}, },
} }
@ -56,7 +58,7 @@ def test_bids_PatternDataladDataGrabber(tmp_path: Path) -> None:
repo_uri = _testing_dataset["example_bids"]["uri"] repo_uri = _testing_dataset["example_bids"]["uri"]
rootdir = "example_bids" rootdir = "example_bids"
repo_commit = _testing_dataset["example_bids"]["id"] repo_commit = _testing_dataset["example_bids"]["commit"]
with PatternDataladDataGrabber( with PatternDataladDataGrabber(
rootdir=rootdir, rootdir=rootdir,
@ -87,11 +89,11 @@ def test_bids_PatternDataladDataGrabber(tmp_path: Path) -> None:
assert dg_meta["class"] == "PatternDataladDataGrabber" assert dg_meta["class"] == "PatternDataladDataGrabber"
assert "uri" in dg_meta assert "uri" in dg_meta
assert dg_meta["uri"] == repo_uri assert dg_meta["uri"] == repo_uri
assert "dataset_commit_id" in dg_meta assert "datalad_commit_id" in dg_meta
assert dg_meta["dataset_commit_id"] == repo_commit assert dg_meta["datalad_commit_id"] == repo_commit
with open(t_sub["T1w"]["path"], "r") as f: with open(t_sub["T1w"]["path"], "r") as f:
assert f.readlines()[0] == "placeholder" assert f.readlines()[0].startswith("placeholder")
def test_bids_PatternDataladDataGrabber_datadir(tmp_path: Path) -> None: def test_bids_PatternDataladDataGrabber_datadir(tmp_path: Path) -> None:

View file

@ -24,13 +24,24 @@ class OasisVBMTestingDatagrabber(BaseDataGrabber):
types = ["VBM_GM"] types = ["VBM_GM"]
synchon commented 2022-11-08 07:24:12 +00:00 (Migrated from github.com)

list of str

`list of str`
synchon commented 2022-11-08 07:25:52 +00:00 (Migrated from github.com)

... -> Dict[str, Path]:?

`... -> Dict[str, Path]:`?
super().__init__(types=types, datadir=datadir) super().__init__(types=types, datadir=datadir)
def __getitem__(self, element: str) -> Dict: def get_element_keys(self) -> List[str]:
"""Get element keys.
Returns
-------
list of str
The element keys.
"""
return ["subject"]
def get_item(self, subject: str) -> Dict[str, Dict]:
"""Implement indexing support. """Implement indexing support.
Parameters Parameters
---------- ----------
element : str subject : str
The element to retrieve. The subject to retrieve.
Returns Returns
------- -------
@ -38,11 +49,10 @@ class OasisVBMTestingDatagrabber(BaseDataGrabber):
The data along with the metadata. The data along with the metadata.
""" """
out = super().__getitem__(element) out = {}
i_sub = int(element.split("-")[1]) - 1 i_sub = int(subject.split("-")[1]) - 1
out["VBM_GM"] = {"path": Path(self._dataset.gray_matter_maps[i_sub])} out["VBM_GM"] = {"path": Path(self._dataset.gray_matter_maps[i_sub])}
# Set the element accordingly
out["meta"]["element"] = {"subject": element}
return out return out
def __enter__(self) -> "OasisVBMTestingDatagrabber": def __enter__(self) -> "OasisVBMTestingDatagrabber":
@ -82,6 +92,17 @@ class SPMAuditoryTestingDatagrabber(BaseDataGrabber):
types = ["BOLD", "T1w"] # TODO: Check that they are T1w types = ["BOLD", "T1w"] # TODO: Check that they are T1w
synchon commented 2022-11-08 07:26:14 +00:00 (Migrated from github.com)

list of str

`list of str`
super().__init__(types=types, datadir=datadir) super().__init__(types=types, datadir=datadir)
def get_element_keys(self) -> List[str]:
"""Get element keys.
Returns
-------
list of str
The element keys.
"""
return ["subject"]
def get_elements(self) -> List[str]: def get_elements(self) -> List[str]:
"""Get elements. """Get elements.
@ -93,13 +114,13 @@ class SPMAuditoryTestingDatagrabber(BaseDataGrabber):
""" """
synchon commented 2022-11-08 07:26:34 +00:00 (Migrated from github.com)

... -> Dict[str, Path]:?

`... -> Dict[str, Path]:`?
return [f"sub{x:03d}" for x in list(range(1, 11))] return [f"sub{x:03d}" for x in list(range(1, 11))]
def __getitem__(self, element: str) -> Dict: def get_item(self, subject: str) -> Dict[str, Dict]:
"""Implement indexing support. """Implement indexing support.
Parameters Parameters
---------- ----------
element : str subject : str
The element to retrieve. The subject to retrieve.
Returns Returns
------- -------
@ -107,20 +128,17 @@ class SPMAuditoryTestingDatagrabber(BaseDataGrabber):
The data along with the metadata. The data along with the metadata.
""" """
out = super().__getitem__(element) out = {}
nilearn_data = datasets.fetch_spm_auditory(subject_id=subject)
nilearn_data = datasets.fetch_spm_auditory(subject_id=element)
fmri_img = image.concat_imgs(nilearn_data.func) # type: ignore fmri_img = image.concat_imgs(nilearn_data.func) # type: ignore
anat_img = image.concat_imgs(nilearn_data.anat) # type: ignore anat_img = image.concat_imgs(nilearn_data.anat) # type: ignore
fmri_fname = self.datadir / f"{element}_bold.nii.gz" fmri_fname = self.datadir / f"{subject}_bold.nii.gz"
anat_fname = self.datadir / f"{element}_T1w.nii.gz" anat_fname = self.datadir / f"{subject}_T1w.nii.gz"
nib.save(fmri_img, fmri_fname) nib.save(fmri_img, fmri_fname)
nib.save(anat_img, anat_fname) nib.save(anat_img, anat_fname)
out["BOLD"] = {"path": fmri_fname} out["BOLD"] = {"path": fmri_fname}
out["T1w"] = {"path": anat_fname} out["T1w"] = {"path": anat_fname}
# Set the element accordingly
out["meta"]["element"] = {"subject": element}
return out return out
@ -176,6 +194,17 @@ class PartlyCloudyTestingDataGrabber(BaseDataGrabber):
) )
synchon commented 2022-11-08 07:27:05 +00:00 (Migrated from github.com)

list of str

`list of str`
return self return self
def get_element_keys(self) -> List[str]:
"""Get element keys.
Returns
-------
list of str
The element keys.
"""
return ["subject"]
def get_elements(self) -> List[str]: def get_elements(self) -> List[str]:
"""Get elements. """Get elements.
@ -187,13 +216,13 @@ class PartlyCloudyTestingDataGrabber(BaseDataGrabber):
""" """
synchon commented 2022-11-08 07:27:32 +00:00 (Migrated from github.com)

... -> Dict[str, Path]:?

`... -> Dict[str, Path]:`?
return [f"sub-{x:02d}" for x in list(range(1, 11))] return [f"sub-{x:02d}" for x in list(range(1, 11))]
def __getitem__(self, element: str) -> Dict: def get_item(self, subject: str) -> Dict[str, Dict]:
"""Implement indexing support. """Implement indexing support.
Parameters Parameters
---------- ----------
element : str subject : str
The element to retrieve. The subject to retrieve.
Returns Returns
------- -------
@ -201,8 +230,8 @@ class PartlyCloudyTestingDataGrabber(BaseDataGrabber):
The data along with the metadata. The data along with the metadata.
""" """
out = super().__getitem__(element) out = {}
i_sub = int(element.split("-")[1]) - 1 i_sub = int(subject.split("-")[1]) - 1
out["BOLD"] = {"path": Path(self._dataset["func"][i_sub])} out["BOLD"] = {"path": Path(self._dataset["func"][i_sub])}
conf_format = "fmriprep" conf_format = "fmriprep"
@ -211,6 +240,4 @@ class PartlyCloudyTestingDataGrabber(BaseDataGrabber):
"format": conf_format, "format": conf_format,
} }
# Set the element accordingly
out["meta"]["element"] = {"subject": element}
return out return out

View file

@ -11,10 +11,13 @@ from pathlib import Path
from subprocess import PIPE, Popen, TimeoutExpired from subprocess import PIPE, Popen, TimeoutExpired
from typing import Dict, NoReturn, Optional, Type, Union from typing import Dict, NoReturn, Optional, Type, Union
from warnings import warn from warnings import warn
import datalad
logger = logging.getLogger("JUNIFER") logger = logging.getLogger("JUNIFER")
# Set up datalad logger level to warning by default
datalad.log.lgr.setLevel(logging.WARNING)
_logging_types = { _logging_types = {
"DEBUG": logging.DEBUG, "DEBUG": logging.DEBUG,
"INFO": logging.INFO, "INFO": logging.INFO,
@ -253,6 +256,7 @@ def configure_logging(
lh.setFormatter(formatter) # set formatter lh.setFormatter(formatter) # set formatter
logger.setLevel(level) # set level logger.setLevel(level) # set level
datalad.log.lgr.setLevel(level) # set level for datalad
logger.addHandler(lh) # set handler logger.addHandler(lh) # set handler
log_versions() # log versions of installed packages log_versions() # log versions of installed packages

View file

@ -27,7 +27,7 @@ with TemporaryDirectory() as tmpdir_name:
f'func/{t_sub}_task-rest_bold.json'] f'func/{t_sub}_task-rest_bold.json']
for fname in fnames: for fname in fnames:
with open(sub_dir / fname, 'w') as f: with open(sub_dir / fname, 'w') as f:
f.write('placeholder') f.write(f'placeholder-{fname}')
ds.save(recursive=True) ds.save(recursive=True)
ds.siblings('add', name='gin', url=dst) ds.siblings('add', name='gin', url=dst)

View file

@ -34,7 +34,7 @@ with TemporaryDirectory() as tmpdir_name:
f'func/{t_sub}_{t_ses}_task-rest_bold.json']) f'func/{t_sub}_{t_ses}_task-rest_bold.json'])
for fname in fnames: for fname in fnames:
with open(ses_dir / fname, 'w') as f: with open(ses_dir / fname, 'w') as f:
f.write('placeholder') f.write('placeholder-{fname}')
ds.save(recursive=True) ds.save(recursive=True)
ds.siblings('add', name='gin', url=dst) ds.siblings('add', name='gin', url=dst)