Skip to content

Commit

Permalink
Add method chaining support to TimeseriesExtractor and CAP classes
Browse files Browse the repository at this point in the history
  • Loading branch information
donishadsmith committed Dec 9, 2024
1 parent 7232a08 commit 1fa67af
Show file tree
Hide file tree
Showing 12 changed files with 112 additions and 15 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ noted in the changelog (i.e new functions or parameters, changes in parameter de
improvements/enhancements. Fixes and modifications will be backwards compatible.
- *.postN* : Consists of only metadata-related changes, such as updates to type hints or doc strings/documentation.

## [0.19.3] - 2024-12-08
### 🚀 New/Added
- Method chaining for several methods in the `CAP` and `TimeseriesExtractor` class.

## [0.19.2] - 2024-12-06
### 🐛 Fixes
- Add type hints to properties.
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# neurocaps
[![Latest Version](https://img.shields.io/pypi/v/neurocaps.svg)](https://pypi.python.org/pypi/neurocaps/)
[![Python Versions](https://img.shields.io/pypi/pyversions/neurocaps.svg)](https://pypi.python.org/pypi/neurocaps/)
[![DOI](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.11642615-teal)](https://doi.org/10.5281/zenodo.14286989)
[![DOI](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.11642615-teal)](https://doi.org/10.5281/zenodo.14322286)
[![Github Repository](https://img.shields.io/badge/Source%20Code-neurocaps-purple)](https://github.com/donishadsmith/neurocaps)
[![Test Status](https://github.com/donishadsmith/neurocaps/actions/workflows/testing.yaml/badge.svg)](https://github.com/donishadsmith/neurocaps/actions/workflows/testing.yaml)
[![codecov](https://codecov.io/github/donishadsmith/neurocaps/graph/badge.svg?token=WS2V7I16WF)](https://codecov.io/github/donishadsmith/neurocaps)
Expand Down Expand Up @@ -230,7 +230,9 @@ extractor.get_bold(
n_cores=None,
pipeline_name="fmriprep", # Can specify if multiple pipelines exists in derivatives directory
verbose=True,
)
).timeseries_to_pickle(
"neurocaps_demo/derivatives", "timeseries.pkl"
) # Method chaining supported in >= 0.19.3 and available for several methods in `CAP` and `TimeseriesExtractor` classes
```
**Output:**
```
Expand All @@ -249,6 +251,7 @@ extractor.get_bold(
# Get CAPs
cap_analysis = CAP(parcel_approach=extractor.parcel_approach)

# Pkl files can also be used as input for `subject_timeseries`
cap_analysis.get_caps(subject_timeseries=extractor.subject_timeseries, n_clusters=2, standardize=True)

# `sharey` only applicable to outer product plots
Expand Down
4 changes: 2 additions & 2 deletions docs/introduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
:alt: Python Versions

.. image:: https://img.shields.io/badge/DOI-10.5281%2Fzenodo.11642615-teal
:target: https://doi.org/10.5281/zenodo.14286989
:target: https://doi.org/10.5281/zenodo.14322286
:alt: DOI

.. image:: https://img.shields.io/badge/Source%20Code-neurocaps-purple
Expand Down Expand Up @@ -86,7 +86,7 @@ Citing
------
::

Smith, D. (2024). neurocaps. Zenodo. https://doi.org/10.5281/zenodo.14286989
Smith, D. (2024). neurocaps. Zenodo. https://doi.org/10.5281/zenodo.14322286

Usage
-----
Expand Down
2 changes: 1 addition & 1 deletion neurocaps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
__all__=["analysis", "extraction"]

# Version in single place
__version__ = "0.19.2"
__version__ = "0.19.3"
47 changes: 46 additions & 1 deletion neurocaps/analysis/cap.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,13 @@ def get_caps(self,
- step: :obj:`int`, default=None
An integer value that controls the progression of the x-axis in plots for the specified
``cluster_selection_method``. When set, only integer values will be displayed on the x-axis.
Returns
-------
self
.. versionadded:: 0.19.3
"""
self._n_cores = n_cores
# Ensure all unique values if n_clusters is a list
Expand Down Expand Up @@ -559,6 +566,8 @@ def get_caps(self,
# Create states dict
self._create_caps_dict()

return self

@staticmethod
def _process_subject_timeseries(subject_timeseries):
if isinstance(subject_timeseries, str) and subject_timeseries.endswith(".pkl"):
Expand Down Expand Up @@ -1430,6 +1439,13 @@ def caps2plot(self,
- vmax: :obj:`float` or :obj:`None`, default=None
The maximum value to display in colormap.
Returns
-------
self
.. versionadded:: 0.19.3
Note
----
**Parcellation Approach**: the "nodes" and "regions" sub-keys are required in ``parcel_approach`` for this
Expand Down Expand Up @@ -1524,6 +1540,8 @@ def caps2plot(self,
if plot_option == "outer_product": self._generate_outer_product_plots(**input_keys, subplots=subplots)
elif plot_option == "heatmap": self._generate_heatmap_plots(**input_keys)

return self

def _create_regions(self, parcellation_name):
# Internal function to create an attribute called `region_caps`. Purpose is to average the values of all nodes
# in a corresponding region to create region heatmaps or outer product plots
Expand Down Expand Up @@ -1947,7 +1965,7 @@ def caps2corr(self,
Returns
-------
`dict[str, pd.DataFrame]`
dict[str, pd.DataFrame]
An instance of a pandas DataFrame for each group.
Note
Expand Down Expand Up @@ -2063,6 +2081,13 @@ def caps2niftis(self,
.. versionchanged:: 0.18.0 "remove_subcortical" key changed to "remove_labels" and default of "k" changed
1 to 3
Returns
-------
self
.. versionadded:: 0.19.3
Note
----
**Assumption**: This function assumes that the background label for the parcellation is zero. Additionaly,
Expand Down Expand Up @@ -2103,6 +2128,8 @@ def caps2niftis(self,

nib.save(stat_map, os.path.join(output_dir, filename))

return self

@staticmethod
def _validate_knn_dict(knn_dict):
valid_atlases = ["Schaefer", "AAL"]
Expand Down Expand Up @@ -2282,6 +2309,13 @@ def caps2surf(self,
- bbox_inches: :obj:`str` or :obj:`None`, default="tight"
Alters size of the whitespace in the saved image.
Returns
-------
self
.. versionadded:: 0.19.3
Note
----
**Parcellation Approach**: ``parcel_approach`` must have the "maps" sub-key containing the path to th
Expand Down Expand Up @@ -2409,6 +2443,8 @@ def caps2surf(self,
try: plt.show(fig) if show_figs else plt.close(fig)
except: plt.show() if show_figs else plt.close()

return self

def caps2radar(self,
output_dir: Optional[os.PathLike]=None,
suffix_title: Optional[str]=None,
Expand Down Expand Up @@ -2588,6 +2624,13 @@ def caps2radar(self,
- engine: {"kaleido", "orca"}, default="kaleido"
Engine used for saving plots.
Returns
-------
self
.. versionadded:: 0.19.3
Note
-----
**Saving Plots**: By default, this function uses "kaleido" (which is also a dependency in this package)
Expand Down Expand Up @@ -2741,6 +2784,8 @@ def caps2radar(self,
filename = filename.replace(".png", ".html")
fig.write_html(os.path.join(output_dir, filename))

return self

def _update_radar_dict(self, group, parcellation_name, radar_dict):
for cap in self._caps[group]:
cap_vector = self._caps[group][cap]
Expand Down
3 changes: 2 additions & 1 deletion neurocaps/analysis/change_dtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ def change_dtype(subject_timeseries_list: Union[list[dict[str, dict[str, NDArray
Returns
-------
`dict[str, dict[str, dict[str, np.ndarray]]]`
dict[str, dict[str, dict[str, np.ndarray]]]
A nested dictionary containing the converted subject timeseries.
Warning
-------
Expand Down
13 changes: 7 additions & 6 deletions neurocaps/analysis/merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ def merge_dicts(subject_timeseries_list: Union[list[dict[str, dict[str, NDArray[
For example, if three dictionaries are provided contain subject 1 with:
- dict 1: run-1 (resting-state)
- dict 2: run-1 and run-2 (Stroop)
- dict 3: run-3 (N-back)
- dict 2: run-1 and run-2 (stroop)
- dict 3: run-3 (n-back)
Then subject 1 in the final merged dictionary will contain:
- run-1: concatenated timeseries from dict 1 and dict 2 (resting-state + Stroop)
- run-2: timeseries from dict 2 (Stroop)
- run-3: timeseries from dict 3 (N-back)
- run-1: concatenated timeseries from dict 1 and dict 2 (resting-state + stroop)
- run-2: timeseries from dict 2 (stroop)
- run-3: timeseries from dict 3 (n-back)
This function supports workflows for identifying similar CAPs across tasks or sessions. Specifically, using the
merged dictionary as input for ``CAP.get_caps`` and the filtered input dictionaries, containing only subjects
Expand Down Expand Up @@ -87,7 +87,8 @@ def merge_dicts(subject_timeseries_list: Union[list[dict[str, dict[str, NDArray[
Returns
-------
`dict[str, dict[str, dict[str, np.ndarray]]]`
dict[str, dict[str, dict[str, np.ndarray]]]
A nested dictionary containing the merged subject timeseries and reduced subject timeseries if specified.
References
----------
Expand Down
3 changes: 2 additions & 1 deletion neurocaps/analysis/standardize.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ def standardize(subject_timeseries_list: Union[list[dict[str, dict[str, NDArray[
Returns
-------
`dict[str, dict[str, dict[str, np.ndarray]]]`
dict[str, dict[str, dict[str, np.ndarray]]]
A nested dictionary containing the standardized subject timeseries.
"""
assert isinstance(subject_timeseries_list, list) and len(subject_timeseries_list) > 0, \
"`subject_timeseries_list` must be a list greater than length 0."
Expand Down
2 changes: 1 addition & 1 deletion neurocaps/analysis/transition_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def transition_matrix(trans_dict: dict[str, pd.DataFrame],
Returns
-------
`dict[str, pd.DataFrame]`
dict[str, pd.DataFrame]
An instance of a pandas DataFrame for each group.
Note
Expand Down
27 changes: 27 additions & 0 deletions neurocaps/extraction/timeseriesextractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,13 @@ def get_bold(self,
flush: :obj:`bool`, default=False
If True, flushes the logged subject-specific information produced during the timeseries extraction process.
Returns
-------
self
.. versionadded:: 0.19.3
Note
----
**Subject Timeseries Dictionary**: This method stores the extracted timeseries of all subjects
Expand Down Expand Up @@ -665,6 +672,8 @@ def get_bold(self,
# Aggregate new timeseries
if isinstance(subject_timeseries, dict): self._subject_timeseries.update(subject_timeseries)

return self

@staticmethod
@lru_cache(maxsize=4)
def _call_layout(bids_dir, pipeline_name):
Expand Down Expand Up @@ -912,6 +921,13 @@ def timeseries_to_pickle(self, output_dir: Union[str, os.PathLike], filename: Op
Name of the file with or without the "pkl" extension.
.. versionchanged:: 0.19.0 ``file_name`` to ``filename``
Returns
-------
self
.. versionadded:: 0.19.3
"""
if not self.subject_timeseries: self._raise_error("Cannot save pickle file")

Expand All @@ -923,6 +939,8 @@ def timeseries_to_pickle(self, output_dir: Union[str, os.PathLike], filename: Op
with open(os.path.join(output_dir, save_filename), "wb") as f:
dump(self._subject_timeseries, f)

return self

def visualize_bold(self,
subj_id: Union[int, str],
run: Union[int, str],
Expand Down Expand Up @@ -977,6 +995,13 @@ def visualize_bold(self,
- bbox_inches: :obj:`str` or :obj:`None`, default="tight"
Alters size of the whitespace in the saved image.
Returns
-------
self
.. versionadded:: 0.19.3
Note
----
**Parcellation Approach**: the "nodes" and "regions" sub-keys are required in ``parcel_approach``.
Expand Down Expand Up @@ -1032,6 +1057,8 @@ def visualize_bold(self,

plt.show() if show_figs else plt.close()

return self

def _get_roi_indices(self, roi_indx, parcellation_name):
if isinstance(roi_indx, int):
plot_indxs = roi_indx
Expand Down
7 changes: 7 additions & 0 deletions tests/test_CAP.py
Original file line number Diff line number Diff line change
Expand Up @@ -947,3 +947,10 @@ def test_check_raise_error():
for i in ["caps", "parcel_approach", "kmeans"]:
with pytest.raises(AttributeError, match=re.escape(error_msg[i])):
CAP._raise_error(i)

def test_chain_CAP():
a = {"show_figs" : False}
cap_analysis = CAP(parcel_approach)
cap_analysis.get_caps(
subject_timeseries=extractor.subject_timeseries,
n_clusters=2).caps2plot(**a)
8 changes: 8 additions & 0 deletions tests/test_TimeseriesExtractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1230,3 +1230,11 @@ def test_check_raise_error():

with pytest.raises(AttributeError, match=re.escape(msg)):
TimeseriesExtractor._raise_error("Cannot do x")

def test_chain_TimeseriesExtractor():
a = {"show_figs": False}
extractor = TimeseriesExtractor()
extractor.get_bold(
bids_dir=bids_dir,
task="rest",
run_subjects=["01"]).timeseries_to_pickle(tmp_dir.name).visualize_bold("01", 0, 0, **a)

0 comments on commit 1fa67af

Please sign in to comment.