Skip to content

Commit

Permalink
Merge pull request #392 from rsagroup/trsa
Browse files Browse the repository at this point in the history
Topological RSA
  • Loading branch information
HeikoSchuett authored Jun 12, 2024
2 parents 069928a + 3b5de51 commit de465dd
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 3 deletions.
2 changes: 2 additions & 0 deletions docs/source/literature_cited.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ References

* Kriegeskorte, N., Mur, M., Ruff, D.A., Kiani, R., Bodurka, J., Esteky, H., Tanaka, K., and Bandettini, P.A. (2008). Matching categorical object representations in inferior temporal cortex of man and monkey. Neuron 60, 1126–1141.

* Lin, B., & Kriegeskorte, N. (2023). The Topology and Geometry of Neural Representations. arXiv preprint arXiv:2309.11028.

* Naselaris, T., Kay, K.N., Nishimoto, S., and Gallant, J.L. (2011). Encoding and decoding in fMRI. Neuroimage 56, 400–410.

* Nili, H., Wingfield, C., Walther, A., Su, L., Marslen-Wilson, W., and Kriegeskorte, N. (2014). A toolbox for representational similarity analysis. PLoS Comput Biol 10, e1003553.
Expand Down
6 changes: 4 additions & 2 deletions docs/source/operations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,10 @@ To transform all RDM entries by a function ``rsatoolbox`` offers specific functi
for the most common transformations and a general ``transform`` function, which takes the
function to be applied as input. These functions are available in ``rsatoolbox.rdm``

The specific transformations are: ``rank_transform``, ``positive_transform``, and ``sqrt_transform``.
They take only a RDMs object as input and compute a rank transform, set all negative values to 0, or compute a square root of each value respectively.
The specific transformations are: ``rank_transform``, ``positive_transform``, ``sqrt_transform`` and ``minmax_transform``.
They take only a RDMs object as input and compute a rank transform, set all negative values to 0, compute a square root of each value respectively, or normalize them into a range of 0 to 1.

There are also and two important transformations as part of the Topological RSA framework, ``geotopological_transform`` and ``geodesic_transform``. These RDM transformations emphasize both the geometric and topological properties of the representations, in terms of neighborhood information, global structure, as well as graph theoretical measures. For more information regarding the Topological RSA framework, please refer to "The Topology and Geometry of Neural Representations" by Lin, B., & Kriegeskorte, N. (2023, arXiv:2309.11028).

For example:

Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ matplotlib
h5py
tqdm
joblib
importlib_resources>=5.12; python_version < "3.9"
importlib_resources>=5.12; python_version < "3.9"
networkx>=3.0
3 changes: 3 additions & 0 deletions src/rsatoolbox/rdm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
from .transform import sqrt_transform
from .transform import positive_transform
from .transform import transform
from .transform import minmax_transform
from .transform import geotopological_transform
from .transform import geodesic_transform
from .calc import calc_rdm
from .calc import calc_rdm_movie
from .calc import calc_rdm_euclidean
Expand Down
89 changes: 89 additions & 0 deletions src/rsatoolbox/rdm/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
from __future__ import annotations
from copy import deepcopy
import numpy as np
import networkx as nx
from scipy.stats import rankdata
from scipy.spatial.distance import squareform
from .rdms import RDMs


Expand Down Expand Up @@ -110,3 +112,90 @@ def transform(rdms, fun):
rdm_descriptors=deepcopy(rdms.rdm_descriptors),
pattern_descriptors=deepcopy(rdms.pattern_descriptors))
return rdms_new


def minmax_transform(rdms: RDMs) -> RDMs:
'''applies a minmax transform to the dissimilarities and returns a new
RDMs object.
Args:
rdms(RDMs): RDMs object
Returns:
rdms_new(RDMs): RDMs object with minmax transformed dissimilarities
'''
dissimilarities = rdms.get_vectors()
for i in range(rdms.n_rdm):
d_max = dissimilarities[i].max()
d_min = dissimilarities[i].min()
dissimilarities[i] = (dissimilarities[i] - d_min) / (d_max - d_min)
meas = 'minmax transformed ' + rdms.dissimilarity_measure
rdms_new = RDMs(dissimilarities,
dissimilarity_measure=meas,
descriptors=deepcopy(rdms.descriptors),
rdm_descriptors=deepcopy(rdms.rdm_descriptors),
pattern_descriptors=deepcopy(rdms.pattern_descriptors))
return rdms_new


def geotopological_transform(rdms: RDMs, l: float, u: float) -> RDMs:
'''applies a geo-topological transform to the dissimilarities and returns
a new RDMs object.
Reference: Lin, B., & Kriegeskorte, N. (2023). The Topology and Geometry
of Neural Representations. arXiv preprint arXiv:2309.11028.
Args:
rdms(RDMs): RDMs object
l(float): lower quantile
u(float): upper quantile
Returns:
rdms_new(RDMs): RDMs object with geotopological transformed dissimilarities
'''
dissimilarities = rdms.get_vectors()
gt_min = np.quantile(dissimilarities, l)
gt_max = np.quantile(dissimilarities, u)
dissimilarities[dissimilarities < gt_min] = 0
dissimilarities[(dissimilarities >= gt_min) & (dissimilarities <= gt_max)] = (
dissimilarities[(dissimilarities >= gt_min) & (dissimilarities <= gt_max)] - gt_min
) / (gt_max - gt_min)
dissimilarities[dissimilarities > gt_max] = 1
meas = 'geo-topological transformed ' + rdms.dissimilarity_measure
rdms_new = RDMs(dissimilarities,
dissimilarity_measure=meas,
descriptors=deepcopy(rdms.descriptors),
rdm_descriptors=deepcopy(rdms.rdm_descriptors),
pattern_descriptors=deepcopy(rdms.pattern_descriptors))
return rdms_new


def geodesic_transform(rdms: RDMs) -> RDMs:
'''applies a geodesic transform to the dissimilarities and returns a
new RDMs object.
Reference: Lin, B., & Kriegeskorte, N. (2023). The Topology and Geometry
of Neural Representations. arXiv preprint arXiv:2309.11028.
Args:
rdms(RDMs): RDMs object
Returns:
rdms_new(RDMs): RDMs object with geodesic transformed dissimilarities
'''
dissimilarities = minmax_transform(rdms).get_vectors()
for i in range(rdms.n_rdm):
G = nx.from_numpy_array(squareform(dissimilarities[i]))
long_edges = []
long_edges = list(
filter(lambda e: e[2] == 1, (e for e in G.edges.data("weight"))))
le_ids = list(e[:2] for e in long_edges)
G.remove_edges_from(le_ids)
dissimilarities[i] = squareform(np.array(nx.floyd_warshall_numpy(G)))
meas = 'geodesic transformed ' + rdms.dissimilarity_measure
rdms_new = RDMs(dissimilarities,
dissimilarity_measure=meas,
descriptors=deepcopy(rdms.descriptors),
rdm_descriptors=deepcopy(rdms.rdm_descriptors),
pattern_descriptors=deepcopy(rdms.pattern_descriptors))
return rdms_new
48 changes: 48 additions & 0 deletions tests/test_rdm.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,54 @@ def test_positive_transform(self):
self.assertEqual(pos_rdm.n_cond, rdms.n_cond)
assert np.all(pos_rdm.dissimilarities >= 0)

def test_minmax_transform(self):
from rsatoolbox.rdm import minmax_transform
dis = np.zeros((8, 10))
mes = "Euclidean"
des = {'subj': 0}
pattern_des = {'type': np.array([0, 1, 2, 2, 4])}
rdm_des = {'session': np.array([0, 1, 2, 2, 4, 5, 6, 7])}
rdms = rsr.RDMs(dissimilarities=dis,
rdm_descriptors=rdm_des,
pattern_descriptors=pattern_des,
dissimilarity_measure=mes,
descriptors=des)
mm_rdm = minmax_transform(rdms)
self.assertEqual(mm_rdm.n_rdm, rdms.n_rdm)
self.assertEqual(mm_rdm.n_cond, rdms.n_cond)

def test_geotopological_transform(self):
from rsatoolbox.rdm import geotopological_transform
dis = np.zeros((8, 10))
mes = "Euclidean"
des = {'subj': 0}
pattern_des = {'type': np.array([0, 1, 2, 2, 4])}
rdm_des = {'session': np.array([0, 1, 2, 2, 4, 5, 6, 7])}
rdms = rsr.RDMs(dissimilarities=dis,
rdm_descriptors=rdm_des,
pattern_descriptors=pattern_des,
dissimilarity_measure=mes,
descriptors=des)
gt_rdm = geotopological_transform(rdms,l=0.2,u=0.8)
self.assertEqual(gt_rdm.n_rdm, rdms.n_rdm)
self.assertEqual(gt_rdm.n_cond, rdms.n_cond)

def test_geodesic_transform(self):
from rsatoolbox.rdm import geodesic_transform
dis = np.random.rand(8, 10)
mes = "Euclidean"
des = {'subj': 0}
pattern_des = {'type': np.array([0, 1, 2, 2, 4])}
rdm_des = {'session': np.array([0, 1, 2, 2, 4, 5, 6, 7])}
rdms = rsr.RDMs(dissimilarities=dis,
rdm_descriptors=rdm_des,
pattern_descriptors=pattern_des,
dissimilarity_measure=mes,
descriptors=des)
gd_rdm = geodesic_transform(rdms)
self.assertEqual(gd_rdm.n_rdm, rdms.n_rdm)
self.assertEqual(gd_rdm.n_cond, rdms.n_cond)

def test_rdm_append(self):
dis = np.zeros((8, 10))
mes = "Euclidean"
Expand Down

0 comments on commit de465dd

Please sign in to comment.