[ENH]: Introduce get_xfm for fetching transformation files #297
|
|
@ -17,17 +17,17 @@ repos:
|
|||
- id: trailing-whitespace
|
||||
args: [--markdown-linebreak-ext=md]
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.15
|
||||
rev: v0.16
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.9.1
|
||||
rev: 24.1.1
|
||||
hooks:
|
||||
- id: black
|
||||
exclude: ^(docs/|examples/|tools/)
|
||||
args: [--check]
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.1.0
|
||||
rev: v0.1.15
|
||||
hooks:
|
||||
- id: ruff
|
||||
types_or: [python, jupyter]
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ Parcellations
|
|||
.. automodule:: junifer.data.parcellations
|
||||
:members:
|
||||
|
||||
|
||||
Coordinates
|
||||
===========
|
||||
|
||||
|
|
@ -16,3 +17,10 @@ Masks
|
|||
|
||||
.. automodule:: junifer.data.masks
|
||||
:members:
|
||||
|
||||
|
||||
Template Spaces
|
||||
===============
|
||||
|
||||
.. automodule:: junifer.data.template_spaces
|
||||
:members:
|
||||
|
|
|
|||
1
docs/changes/newsfragments/297.feature
Normal file
|
|
@ -0,0 +1 @@
|
|||
Introduce :func:`.get_xfm` to fetch transformation files for moving between template spaces by `Synchon Mandal`_
|
||||
|
|
@ -141,6 +141,7 @@ def _validate_verbose(
|
|||
-------
|
||||
str or int
|
||||
The validated value.
|
||||
|
||||
"""
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
|
|
|
|||
|
|
@ -575,6 +575,7 @@ def _queue_slurm(
|
|||
(default None).
|
||||
config : dict
|
||||
The configuration to be used for queueing the job.
|
||||
|
||||
"""
|
||||
pass
|
||||
# logger.debug("Creating SLURM job")
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ def test_get_python_information() -> None:
|
|||
def test_get_dependency_information_short() -> None:
|
||||
"""Test short version of _get_dependency_information()."""
|
||||
dependency_information = _get_dependency_information(long_=False)
|
||||
assert list(dependency_information.keys()) == [
|
||||
dependency_list = [
|
||||
"click",
|
||||
"numpy",
|
||||
"scipy",
|
||||
|
|
@ -42,14 +42,20 @@ def test_get_dependency_information_short() -> None:
|
|||
"nilearn",
|
||||
"sqlalchemy",
|
||||
"ruamel.yaml",
|
||||
"httpx",
|
||||
]
|
||||
if int(pl.python_version_tuple()[1]) < 10:
|
||||
dependency_list.append("importlib_metadata")
|
||||
assert frozenset(dependency_information.keys()) == frozenset(
|
||||
dependency_list
|
||||
)
|
||||
|
||||
|
||||
def test_get_dependency_information_long() -> None:
|
||||
"""Test long version of _get_dependency_information()."""
|
||||
dependency_information = _get_dependency_information(long_=True)
|
||||
dependency_information_keys = list(dependency_information.keys())
|
||||
for key in [
|
||||
dependency_list = [
|
||||
"click",
|
||||
"numpy",
|
||||
"scipy",
|
||||
|
|
@ -59,7 +65,9 @@ def test_get_dependency_information_long() -> None:
|
|||
"nilearn",
|
||||
"sqlalchemy",
|
||||
"ruamel.yaml",
|
||||
]:
|
||||
"httpx",
|
||||
]
|
||||
for key in dependency_list:
|
||||
assert key in dependency_information_keys
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,4 +25,6 @@ from .masks import (
|
|||
get_mask,
|
||||
)
|
||||
|
||||
from .template_spaces import get_xfm
|
||||
|
||||
from . import utils
|
||||
|
|
|
|||
91
junifer/data/template_spaces.py
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
"""Provide functions for template spaces."""
|
||||
|
Yeah we can, since I don't have permission to make a repo in the org, I made it in my account. Yeah we can, since I don't have permission to make a repo in the org, I made it in my account.
Now you have ! Now you have !
Transferred. Transferred.
|
||||
|
||||
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
||||
# License: AGPL
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
import httpx
|
||||
|
||||
from ..utils import logger, raise_error
|
||||
|
||||
|
||||
def get_xfm(
|
||||
src: str, dst: str, xfms_dir: Union[str, Path, None] = None
|
||||
) -> Path: # pragma: no cover
|
||||
"""Fetch warp files to convert from ``src`` to ``dst``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
src : str
|
||||
The template space to transform from.
|
||||
dst : str
|
||||
The template space to transform to.
|
||||
xfms_dir : str or pathlib.Path, optional
|
||||
Path where the retrieved transformation files are stored.
|
||||
The default location is "$HOME/junifer/data/xfms" (default None).
|
||||
|
||||
Returns
|
||||
-------
|
||||
pathlib.Path
|
||||
The path to the transformation file.
|
||||
|
||||
Raises
|
||||
------
|
||||
RuntimeError
|
||||
If there is a problem fetching files.
|
||||
|
||||
"""
|
||||
if xfms_dir is None:
|
||||
xfms_dir = Path().home() / "junifer" / "data" / "xfms"
|
||||
logger.debug(f"Creating xfm directory at: {xfms_dir.resolve()}")
|
||||
# Create default junifer data directory if not present
|
||||
xfms_dir.mkdir(exist_ok=True, parents=True)
|
||||
# Convert str to Path
|
||||
elif not isinstance(xfms_dir, Path):
|
||||
xfms_dir = Path(xfms_dir)
|
||||
|
||||
# Set local file prefix
|
||||
xfm_file_prefix = f"{src}_to_{dst}"
|
||||
# Set local file dir
|
||||
xfm_file_dir = xfms_dir / xfm_file_prefix
|
||||
# Create local directory if not present
|
||||
xfm_file_dir.mkdir(exist_ok=True, parents=True)
|
||||
# Set file name with extension
|
||||
xfm_file = f"{src}_to_{dst}_Composite.h5"
|
||||
# Set local file path
|
||||
xfm_file_path = xfm_file_dir / xfm_file
|
||||
# Check if the file exists
|
||||
if xfm_file_path.exists():
|
||||
logger.info(
|
||||
f"Found existing xfm file for {src} to {dst} at "
|
||||
f"{xfm_file_path.resolve()}"
|
||||
)
|
||||
return xfm_file_path
|
||||
|
||||
# Set URL
|
||||
url = (
|
||||
"https://gin.g-node.org/juaml/human-template-xfms/raw/main/xfms/"
|
||||
f"{xfm_file_prefix}/{xfm_file}"
|
||||
)
|
||||
# Create the file before proceeding
|
||||
xfm_file_path.touch()
|
||||
|
||||
logger.info(f"Downloading xfm file for {src} to {dst} from {url}")
|
||||
# Steam response
|
||||
with httpx.stream("GET", url) as resp:
|
||||
try:
|
||||
resp.raise_for_status()
|
||||
except httpx.HTTPError as exc:
|
||||
raise_error(
|
||||
f"Error response {exc.response.status_code} while "
|
||||
f"requesting {exc.request.url!r}",
|
||||
klass=RuntimeError,
|
||||
)
|
||||
else:
|
||||
with open(xfm_file_path, "ab") as f:
|
||||
for chunk in resp.iter_bytes():
|
||||
|
Is Is `iter_bytes` still streaming? or the content is already in memory? If it is still streaming, an exception might occur here an it will not be catch.
It's a generator basically thus it's still streaming. I don't find any exceptions being raised there in It's a generator basically thus it's still streaming. I don't find any exceptions being raised there in `httpx`'s source code.
Not from httpx, but mostly from an underlying connection issue if the connection is cut Not from httpx, but mostly from an underlying connection issue if the connection is cut
You will get error from You will get error from `httpcore`, which is the underlying HTTP library, like you get from `urllib3` when using `requests`.
Exactly, I thought of catching any exception and giving a more user-friendly error message of what's going Exactly, I thought of catching any exception and giving a more user-friendly error message of what's going
Ideally it should be dealt by the underlying http lib. You won't get an exception to catch when doing Ideally it should be dealt by the underlying http lib. You won't get an exception to catch when doing `iter_bytes()`, having an exception block there would not serve the purpose imo.
|
||||
f.write(chunk)
|
||||
|
||||
return xfm_file_path
|
||||
|
|
@ -36,6 +36,7 @@ def test_closest_resolution(
|
|||
The valid resolutions.
|
||||
expected: float
|
||||
The expected result.
|
||||
|
||||
"""
|
||||
assert closest_resolution(resolution, valid_resolutions) == expected
|
||||
assert (
|
||||
|
|
|
|||
|
|
@ -343,6 +343,7 @@ def test_nilearn_compute_masks(
|
|||
Parameters to pass to the function.
|
||||
resample : bool
|
||||
Whether to resample the mask to the target data.
|
||||
|
||||
"""
|
||||
reader = DefaultDataReader()
|
||||
with SPMAuditoryTestingDataGrabber() as dg:
|
||||
|
|
@ -423,6 +424,7 @@ def test_get_mask_multiple(
|
|||
Masks to get, junifer style.
|
||||
params : dict
|
||||
Parameters to pass to the intersect_masks function.
|
||||
|
||||
"""
|
||||
reader = DefaultDataReader()
|
||||
with SPMAuditoryTestingDataGrabber() as dg:
|
||||
|
|
|
|||
30
junifer/data/tests/test_template_spaces.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
"""Provide tests for template spaces."""
|
||||
|
||||
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
||||
# License: AGPL
|
||||
|
||||
import socket
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from junifer.data import get_xfm
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
socket.gethostname() != "juseless",
|
||||
|
Why only for juseless? Why only for juseless?
The download takes well around 10 mins from Gin. The download takes well around 10 mins from Gin.
damn, we need to create a "slow test" suite that we use it to check only when about to merge or something like that. For the moment we keep it like this. damn, we need to create a "slow test" suite that we use it to check only when about to merge or something like that. For the moment we keep it like this.
Yeah that's a good idea. Well the essential issue is that Gin is not really as powerful as GitHub when you consider infrastructure, so the octet-stream downloads are not cached from what I understand. Yeah that's a good idea. Well the essential issue is that Gin is not really as powerful as GitHub when you consider infrastructure, so the octet-stream downloads are not cached from what I understand.
|
||||
reason="only for juseless",
|
||||
)
|
||||
def test_get_xfm(tmp_path: Path) -> None:
|
||||
"""Test warp file fetching.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tmp_path : pathlib.Path
|
||||
The path to the test directory.
|
||||
|
||||
"""
|
||||
xfm_path = get_xfm(
|
||||
src="MNI152NLin6Asym", dst="MNI152NLin2009cAsym", xfms_dir=tmp_path
|
||||
)
|
||||
assert isinstance(xfm_path, Path)
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
"""Provide utilities for data module."""
|
||||
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
|
|
@ -24,6 +25,7 @@ def closest_resolution(
|
|||
-------
|
||||
float or int
|
||||
The closest valid resolution.
|
||||
|
||||
"""
|
||||
# Convert list of int to numpy.ndarray
|
||||
if not isinstance(valid_resolution, np.ndarray):
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ def _apply_mask_and_get_affinity(
|
|||
A : scipy.sparse.lil_matrix
|
||||
Contains the boolean indices for each sphere.
|
||||
shape: (number of seeds, number of voxels)
|
||||
|
||||
"""
|
||||
seeds = list(seeds)
|
||||
|
||||
|
|
@ -204,6 +205,7 @@ def _iter_signals_from_spheres(
|
|||
mask_img : Niimg-like object, optional
|
||||
See :ref:`extracting_data`.
|
||||
Mask to apply to regions before extracting signals.
|
||||
|
||||
"""
|
||||
X, A = _apply_mask_and_get_affinity(
|
||||
seeds, niimg, radius, allow_overlap, mask_img=mask_img
|
||||
|
|
|
|||
|
|
@ -184,6 +184,7 @@ class ALFFBase(BaseMarker):
|
|||
other data kind that needs to be used in the computation. For
|
||||
example, the functional connectivity markers can make use of the
|
||||
confounds if available (default None).
|
||||
|
||||
"""
|
||||
raise_error(
|
||||
"_postprocess must be implemented", klass=NotImplementedError
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ class ALFFParcels(ALFFBase):
|
|||
ALFF/fALFF are computed using a bandpass butterworth filter. See
|
||||
:func:`scipy.signal.butter` and :func:`scipy.signal.filtfilt` for more
|
||||
details.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ class ALFFSpheres(ALFFBase):
|
|||
ALFF/fALFF are computed using a bandpass butterworth filter. See
|
||||
:func:`scipy.signal.butter` and :func:`scipy.signal.filtfilt` for more
|
||||
details.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ class CrossParcellationFC(BaseMarker):
|
|||
name : str, optional
|
||||
The name of the marker. If None, will use the class name
|
||||
(default None).
|
||||
|
||||
"""
|
||||
|
||||
_DEPENDENCIES: ClassVar[Set[str]] = {"nilearn"}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ class EdgeCentricFCParcels(FunctionalConnectivityBase):
|
|||
|
||||
* ``data`` : the actual computed values as a numpy.ndarray
|
||||
* ``col_names`` : the column labels for the computed values as list
|
||||
|
||||
"""
|
||||
parcel_aggregation = ParcelAggregation(
|
||||
parcellation=self.parcellation,
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ class FunctionalConnectivityBase(BaseMarker):
|
|||
-------
|
||||
list of str
|
||||
The list of data types that can be used as input for this marker.
|
||||
|
||||
"""
|
||||
return ["BOLD"]
|
||||
|
||||
|
|
|
|||
|
|
@ -500,9 +500,7 @@ def _kendall_w_reho(
|
|||
numerator = (12 * np.sum(np.square(np.sum(timeseries_ranks, axis=0)))) - (
|
||||
3 * m**2 * n * (n + 1) ** 2
|
||||
)
|
||||
denominator = (m**2 * n * (n**2 - 1)) - (
|
||||
m * np.sum(tied_rank_corrections)
|
||||
)
|
||||
denominator = (m**2 * n * (n**2 - 1)) - (m * np.sum(tied_rank_corrections))
|
||||
|
||||
if denominator == 0:
|
||||
kcc = 1.0
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ class TemporalSNRBase(BaseMarker):
|
|||
-------
|
||||
list of str
|
||||
The list of data types that can be used as input for this marker.
|
||||
|
||||
"""
|
||||
return ["BOLD"]
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ def test_UpdateMetaMixin(
|
|||
The dependencies of the pipeline step.
|
||||
expected : set
|
||||
The expected dependencies.
|
||||
|
||||
"""
|
||||
|
||||
class TestUpdateMetaMixin(UpdateMetaMixin):
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ class BaseFeatureStorage(ABC):
|
|||
The element as a dictionary.
|
||||
meta : dict
|
||||
The metadata as a dictionary.
|
||||
|
||||
"""
|
||||
raise_error(
|
||||
msg="Concrete classes need to implement store_metadata().",
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ def _create_chunk(
|
|||
------
|
||||
ValueError
|
||||
If `kind` is not one of ['vector', 'matrix', 'timeseries'].
|
||||
|
||||
"""
|
||||
if kind in ["vector", "matrix"]:
|
||||
features_data = np.concatenate(chunk_data, axis=-1)
|
||||
|
|
@ -678,9 +679,13 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|||
elif isinstance(data, list):
|
||||
if self.force_float32:
|
||||
data = [
|
||||
x.astype(dtype=np.dtype("float32"), casting="same_kind")
|
||||
(
|
||||
x.astype(
|
||||
dtype=np.dtype("float32"), casting="same_kind"
|
||||
)
|
||||
if x.dtype == np.dtype("float64")
|
||||
else x
|
||||
)
|
||||
for x in data
|
||||
]
|
||||
# Handle cases for existing and new entry
|
||||
|
|
|
|||
|
|
@ -366,6 +366,7 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|||
The element as a dictionary.
|
||||
meta : dict
|
||||
The metadata as a dictionary.
|
||||
|
||||
"""
|
||||
# Get sqlalchemy engine
|
||||
engine = self.get_engine(element=element)
|
||||
|
|
|
|||
|
|
@ -838,6 +838,7 @@ def _create_data_to_store(n_elements: int, kind: str) -> Tuple[str, Dict]:
|
|||
The meta md5.
|
||||
dict
|
||||
The data to store.
|
||||
|
||||
"""
|
||||
all_data = []
|
||||
t_md5 = None
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ def element_to_prefix(element: Dict) -> str:
|
|||
-------
|
||||
str
|
||||
The element converted to prefix.
|
||||
|
||||
"""
|
||||
logger.debug(f"Converting element {element} to prefix.")
|
||||
prefix = "element"
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ def get_testing_data(fname: str) -> Path:
|
|||
-------
|
||||
pathlib.Path
|
||||
The absolute path to the file.
|
||||
|
||||
"""
|
||||
t_path = Path(__file__).parent / "data" / fname
|
||||
if not t_path.exists():
|
||||
|
|
|
|||
|
|
@ -41,11 +41,12 @@ dependencies = [
|
|||
"datalad>=0.15.4,<0.20",
|
||||
"pandas>=1.4.0,<2.2",
|
||||
"nibabel>=3.2.0,<5.11",
|
||||
"nilearn>=0.9.0,<=0.11.0",
|
||||
"nilearn>=0.9.0,<=0.10.2",
|
||||
"sqlalchemy>=1.4.27,<=2.1.0",
|
||||
"ruamel.yaml>=0.17,<0.18",
|
||||
"importlib_metadata; python_version<'3.10'",
|
||||
"h5py>=3.8.0,<3.10",
|
||||
"httpx[http2]==0.26.0",
|
||||
]
|
||||
|
Is it that strict the dependency? Is it that strict the dependency?
It's always recommended to keep it like so when dealing with libraries which aren't 1.0 yet as the APIs can change very often. It's always recommended to keep it like so when dealing with libraries which aren't 1.0 yet as the APIs can change very often.
perfect perfect
|
||||
dynamic = ["version"]
|
||||
|
||||
|
|
@ -185,6 +186,7 @@ known-third-party =[
|
|||
"nilearn",
|
||||
"sqlalchemy",
|
||||
"yaml",
|
||||
"httpx",
|
||||
"bct",
|
||||
"neurokit2",
|
||||
"pytest",
|
||||
|
|
|
|||
Can we make it the juaml organization in g-node.org?