Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
77ef8a3
add stimulus_domain module
ganow Dec 14, 2023
bf1d4ec
update type annotation
ganow Dec 14, 2023
3ba67e6
add dataset module
ganow Dec 14, 2023
f3a84d5
add interface for the feature inversion pipeline
ganow Dec 14, 2023
ab354a2
add encoder module
ganow Dec 14, 2023
083369f
refactor type annotations
ganow Dec 14, 2023
b29f79f
update encoder interface
ganow Dec 14, 2023
5d81a82
add generator module
ganow Dec 14, 2023
5243673
update generator module
ganow Dec 14, 2023
2e0db92
update docstring
ganow Dec 15, 2023
463f7b5
add latent module
ganow Dec 15, 2023
3e50a3c
remove unused import
ganow Dec 15, 2023
c8f99c3
update encoder interface
ganow Dec 15, 2023
5e9e77c
update docstring
ganow Dec 15, 2023
1f6032f
add critic module
ganow Dec 15, 2023
07196b3
update docstring
ganow Dec 15, 2023
c780262
add feature inversion pipeline
ganow Dec 15, 2023
b753140
update imports
ganow Dec 15, 2023
7a361df
rename XYZBase -> BaseXYZ
ganow Dec 15, 2023
8bb09b6
Remove dependency on nn.Module from base class && use base class for …
ganow Dec 15, 2023
d0e4543
add naive MSE critic
ganow Dec 15, 2023
5afb9a6
tentative implementation of callback
ganow Dec 19, 2023
9305df9
improve APIs and docstring
ganow Dec 19, 2023
b51c573
update docstring
ganow Dec 19, 2023
23234a3
update docstrings
ganow Dec 19, 2023
6b3c51f
tentative implementation of the callback for W&B
ganow Dec 19, 2023
34c665c
change generator API
ganow Dec 20, 2023
7dc43e4
change critic API
ganow Dec 20, 2023
bd20214
change encoder API
ganow Dec 20, 2023
e4e6e13
change latent API
ganow Dec 20, 2023
ad7e53f
remove unused import
ganow Dec 20, 2023
d785b94
Merge branch 'dev' into feature-inversion-pipeline
ganow Dec 20, 2023
43dc7db
add test cases for critic module
ganow Dec 20, 2023
5af9091
update ciritic API so that it pass test cases
ganow Dec 20, 2023
6f30024
update test cases for critic module
ganow Dec 20, 2023
ac87094
update test cases
ganow Dec 20, 2023
19aeefc
update docstring
ganow Dec 20, 2023
3925303
update domain API
ganow Dec 20, 2023
fdb957f
WIP update of domain API
ganow Dec 20, 2023
393887b
update domain API
ganow Dec 20, 2023
aeb1679
update docstring and warnings
ganow Dec 20, 2023
4147d7f
update docstring
ganow Dec 20, 2023
f26b971
update docstring
ganow Dec 20, 2023
678b323
test cases for encoder
ganow Dec 20, 2023
7f15a52
update docstring based on the comments
ganow Dec 20, 2023
18218f9
fixed docstring
ganow Dec 20, 2023
f838bf0
fixed bug
ganow Dec 20, 2023
cdc1baf
update control flow
ganow Dec 20, 2023
8ea441d
update docstring for callback
ganow Dec 20, 2023
71ce612
explicitly define all the available event types in the base callback …
ganow Dec 20, 2023
34ecc3a
change the default media_interval to be -1
ganow Dec 20, 2023
1c7129b
bugfix in docstring
ganow Dec 20, 2023
45995b4
update docstring
ganow Dec 20, 2023
e9912bf
update docstring so that it explictly states which domain to use
ganow Dec 20, 2023
bb0ff97
update test case
ganow Dec 21, 2023
1b8be76
fix docstring
ganow Dec 21, 2023
f4a5f36
remove device option from encoder API
ganow Dec 21, 2023
8b320b6
main statement in test cases
ganow Dec 21, 2023
f67228e
update reset function
ganow Dec 21, 2023
c9aa64d
update docstring
ganow Dec 21, 2023
87f92c3
update docstring
ganow Dec 21, 2023
8c09756
test case for generator module
ganow Dec 21, 2023
e8d88af
update test case
ganow Dec 21, 2023
ceda337
bugfix
ganow Dec 21, 2023
8f13ebb
make the list of event types in the callback object as minimum as pos…
ganow Dec 21, 2023
72021f8
deprecation warning
ganow Dec 21, 2023
54add00
move callback.py to bdpy.pipeline
ganow Dec 21, 2023
75ddefc
add callback validation
ganow Dec 21, 2023
6c46bbf
bugfix
ganow Dec 21, 2023
2d5bd86
use .clone().detach() when pipeline passes tensor to callback
ganow Dec 21, 2023
eeb64c6
add dependency on torchvision
ganow Dec 21, 2023
c3babf4
add FixedResolutionDomain
ganow Dec 21, 2023
9458842
update test case for reset_all_parameters
ganow Dec 21, 2023
718702a
update test case
ganow Dec 22, 2023
cd6529a
NNModuleEncoder
ganow Dec 22, 2023
7977cd4
update test case
ganow Dec 25, 2023
5dee9d9
rename pipeline -> task
ganow Dec 27, 2023
4828f45
base class for the task object
ganow Dec 27, 2023
b01c955
organize APIs
ganow Dec 27, 2023
00fb71a
docstring
ganow Dec 27, 2023
5a521b4
rename `reset_all_parameters` -> `call_reset_parameters` and add warning
ganow Dec 28, 2023
0404f71
move definition of internal domain to core module
ganow Jan 4, 2024
e0bae89
test cases for callback module
ganow Jan 4, 2024
2e637f7
update test case for callback module
ganow Jan 5, 2024
772c049
update test cases for BaseCallback
ganow Jan 5, 2024
60ff809
test case for callback handler
ganow Jan 5, 2024
ed03f17
remove unsupported codes (WandB logging)
ganow Jan 5, 2024
4d3c8af
changed interface around base callback
ganow Jan 5, 2024
49b3542
first commit
myaokai Jan 23, 2024
fb203f1
create test files
myaokai Jan 23, 2024
3210670
add TestBaseLatent
myaokai Jan 24, 2024
31d3ba5
Update tests/recon/torch/modules/test_latent.py
myaokai Jan 24, 2024
4f367b5
Update tests/recon/torch/modules/test_latent.py
myaokai Jan 24, 2024
3660f40
Update tests/recon/torch/modules/test_latent.py
myaokai Jan 24, 2024
33f2c9f
update test latent
myaokai Jan 25, 2024
b03b68b
create test_core
myaokai Jan 25, 2024
6cef2f2
update test_core
myaokai Jan 25, 2024
547d337
create test_feature_domain
myaokai Jan 25, 2024
b038a75
create test_image_domain
myaokai Jan 26, 2024
6605f28
Update test_image_domain.py
myaokai Jan 30, 2024
7cbfc0e
create task/test_core
myaokai Jan 30, 2024
ca3b162
create inversion test
myaokai Feb 6, 2024
948d5a5
Update test_inversion.py
myaokai Feb 8, 2024
bd2e180
Update test_inversion.py
myaokai Feb 8, 2024
3f69381
Update tests/dl/torch/domain/test_core.py
myaokai Mar 21, 2024
291807f
Update tests/dl/torch/domain/test_core.py
myaokai Mar 21, 2024
c87d8bc
Update tests/dl/torch/domain/test_image_domain.py
myaokai Mar 21, 2024
7f0c323
Update tests/dl/torch/domain/test_image_domain.py
myaokai Mar 21, 2024
13da19f
Update tests/dl/torch/domain/test_core.py
myaokai Mar 21, 2024
46d4d10
Update tests/recon/torch/modules/test_latent.py
myaokai Mar 21, 2024
75a45ab
Update tests/recon/torch/task/test_inversion.py
myaokai Mar 21, 2024
bbae3d7
Update test_core.py
myaokai Mar 21, 2024
556e6f0
fix
myaokai Mar 21, 2024
7ddd4fb
fix
myaokai Mar 21, 2024
d86e526
fix
myaokai Mar 21, 2024
ff116ef
Merge pull request #85 from KamitaniLab/myaokai/feat_inv_test
ShuntaroAoki Apr 4, 2024
f87016c
change the name of the abstractmethod of a task to run()
ganow Jul 26, 2024
6146012
automatically run reset_states() when the task is fired
ganow Jul 26, 2024
53e1403
critic.compare() -> critic.evaluate()
ganow Jul 26, 2024
02dd872
Merge branch 'dev' into feature-inversion-pipeline
ShuntaroAoki Jul 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions bdpy/dl/torch/dataset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
from __future__ import annotations

from typing import Iterable, Callable, Dict

from pathlib import Path

from PIL import Image
import numpy as np
from torch.utils.data import Dataset

from bdpy.dataform import DecodedFeatures, Features


_FeatureTypeNP = Dict[str, np.ndarray]


def _removesuffix(s: str, suffix: str) -> str:
"""Remove suffix from string.

Note
----
This function is available from Python 3.9 as `str.removesuffix`. We can
remove this function when we drop support for Python 3.8.

Parameters
----------
s : str
String.
suffix : str
Suffix to remove.

Returns
-------
str
String without suffix.
"""
if suffix and s.endswith(suffix):
return s[: -len(suffix)]
return s[:]


class FeaturesDataset(Dataset):
"""Dataset of features.

Parameters
----------
root_path : str | Path
Path to the root directory of features.
layer_path_names : Iterable[str]
List of layer path names. Each layer path name is used to get features
from the root directory so that the layer path name must be a part of
the path to the layer.
stimulus_names : list[str], optional
List of stimulus names. If None, all stimulus names are used.
transform : callable, optional
Callable object which is used to transform features. The callable object
must take a dict of features and return a dict of features.
"""

def __init__(
self,
root_path: str | Path,
layer_path_names: Iterable[str],
stimulus_names: list[str] | None = None,
transform: Callable[[_FeatureTypeNP], _FeatureTypeNP] | None = None,
):
self._features_store = Features(Path(root_path).as_posix())
self._layer_path_names = layer_path_names
if stimulus_names is None:
stimulus_names = self._features_store.labels
self._stimulus_names = stimulus_names
self._transform = transform

def __len__(self) -> int:
return len(self._stimulus_names)

def __getitem__(self, index: int) -> _FeatureTypeNP:
stimulus_name = self._stimulus_names[index]
features = {}
for layer_path_name in self._layer_path_names:
feature = self._features_store.get(
layer=layer_path_name, label=stimulus_name
)
feature = feature[0] # NOTE: remove batch axis
features[layer_path_name] = feature
if self._transform is not None:
features = self._transform(features)
return features


class DecodedFeaturesDataset(Dataset):
"""Dataset of decoded features.

Parameters
----------
root_path : str | Path
Path to the root directory of decoded features.
layer_path_names : Iterable[str]
List of layer path names. Each layer path name is used to get features
from the root directory so that the layer path name must be a part of
the path to the layer.
subject_id : str
ID of the subject.
roi : str
ROI name.
stimulus_names : list[str], optional
List of stimulus names. If None, all stimulus names are used.
transform : callable, optional
Callable object which is used to transform features. The callable object
must take a dict of features and return a dict of features.
"""

def __init__(
self,
root_path: str | Path,
layer_path_names: Iterable[str],
subject_id: str,
roi: str,
stimulus_names: list[str] | None = None,
transform: Callable[[_FeatureTypeNP], _FeatureTypeNP] | None = None,
):
self._decoded_features_store = DecodedFeatures(Path(root_path).as_posix())
self._layer_path_names = layer_path_names
self._subject_id = subject_id
self._roi = roi
if stimulus_names is None:
stimulus_names = self._decoded_features_store.labels
assert stimulus_names is not None
self._stimulus_names = stimulus_names
self._transform = transform

def __len__(self) -> int:
return len(self._stimulus_names)

def __getitem__(self, index: int) -> _FeatureTypeNP:
stimulus_name = self._stimulus_names[index]
decoded_features = {}
for layer_path_name in self._layer_path_names:
decoded_feature = self._decoded_features_store.get(
layer=layer_path_name,
label=stimulus_name,
subject=self._subject_id,
roi=self._roi,
)
decoded_feature = decoded_feature[0] # NOTE: remove batch axis
decoded_features[layer_path_name] = decoded_feature
if self._transform is not None:
decoded_features = self._transform(decoded_features)
return decoded_features


class ImageDataset(Dataset):
"""Dataset of images.

Parameters
----------
root_path : str | Path
Path to the root directory of images.
stimulus_names : list[str], optional
List of stimulus names. If None, all stimulus names are used.
extension : str, optional
Extension of the image files.
"""

def __init__(
self,
root_path: str | Path,
stimulus_names: list[str] | None = None,
extension: str = "jpg",
):
self.root_path = root_path
if stimulus_names is None:
stimulus_names = [
_removesuffix(path.name, "." + extension)
for path in Path(root_path).glob(f"*{extension}")
]
self._stimulus_names = stimulus_names
self._extension = extension

def __len__(self):
return len(self._stimulus_names)

def __getitem__(self, index: int):
stimulus_name = self._stimulus_names[index]
image = Image.open(Path(self.root_path) / f"{stimulus_name}.{self._extension}")
image = image.convert("RGB")
return np.array(image) / 255.0, stimulus_name


class RenameFeatureKeys:
def __init__(self, mapping: dict[str, str]):
self._mapping = mapping

def __call__(self, features: _FeatureTypeNP) -> _FeatureTypeNP:
return {self._mapping.get(key, key): value for key, value in features.items()}
1 change: 1 addition & 0 deletions bdpy/dl/torch/domain/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .core import Domain, InternalDomain, IrreversibleDomain, ComposedDomain, KeyValueDomain
175 changes: 175 additions & 0 deletions bdpy/dl/torch/domain/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Iterable, TypeVar, Generic
import warnings

import torch.nn as nn

_T = TypeVar("_T")


class Domain(nn.Module, ABC, Generic[_T]):
"""Base class for stimulus domain.

This class is used to convert data between each domain and library's internal common space.
Suppose that we have two functions `f: X -> Y_1` and `g: Y_2 -> Z` and want to compose them.
Here, `X`, `Y_1`, `Y_2`, and `Z` are different domains and assume that `Y_1` and `Y_2` are
the similar domain that can be converted to each other.
Then, we can compose `f` and `g` as `g . t . f(x)`, where `t: Y_1 -> Y_2` is the domain
conversion function. This class is used to implement `t`.

The subclasses of this class should implement `send` and `receive` methods. The `send` method
converts data from the original domain (`Y_1` or `Y_2`) to the internal common space (`Y_0`),
and the `receive` method converts data from the internal common space to the original domain.
By implementing domain class for `Y_1` and `Y_2`, we can construct the domain conversion function
`t` as `t = Y_2.receive . Y_1.send`.

Note that the subclasses of this class do not necessarily guarantee the reversibility of `send`
and `receive` methods. If the domain conversion is irreversible, the subclasses should inherit
`IrreversibleDomain` class instead of this class.
"""

@abstractmethod
def send(self, x: _T) -> _T:
"""Send stimulus to the internal common space from each domain.

Parameters
----------
x : _T
Data in the original domain.

Returns
-------
_T
Data in the internal common space.
"""
pass

@abstractmethod
def receive(self, x: _T) -> _T:
"""Receive data from the internal common space to each domain.

Parameters
----------
x : _T
Data in the internal common space.

Returns
-------
_T
Data in the original domain.
"""
pass


class InternalDomain(Domain, Generic[_T]):
"""The internal common space.

The domain class which defines the internal common space. This class
receives and sends data as it is.
"""

def send(self, x: _T) -> _T:
return x

def receive(self, x: _T) -> _T:
return x


class IrreversibleDomain(Domain, Generic[_T]):
"""The domain which cannot be reversed.

This class is used to convert data between each domain and library's
internal common space. Note that the subclasses of this class do not
guarantee the reversibility of `send` and `receive` methods.
"""

def __init__(self) -> None:
super().__init__()
warnings.warn(
f"{self.__class__.__name__} is an irreversible domain. " \
"It does not guarantee the reversibility of `send` and `receive` " \
"methods. Please use the combination of `send` and `receive` methods " \
"with caution.",
RuntimeWarning,
)

def send(self, x: _T) -> _T:
return x

def receive(self, x: _T) -> _T:
return x


class ComposedDomain(Domain, Generic[_T]):
"""The domain composed of multiple sub-domains.

Suppose we have list of domain objects `domains = [d_0, d_1, ..., d_n]`.
Then, `ComposedDomain(domains)` accesses the data in the original domain `D`
as `d_n.receive . ... d_1.receive . d_0.receive(x)` from the internal common space `D_0`.

Parameters
----------
domains : Iterable[Domain]
Sub-domains to compose.

Examples
--------
>>> import numpy as np
>>> import torch
>>> from bdpy.dl.torch.domain import ComposedDomain
>>> from bdpy.dl.torch.domain.image_domain import AffineDomain, BGRDomain
>>> composed_domain = ComposedDomain([
... AffineDomain(0.5, 1),
... BGRDomain(),
... ])
>>> image = torch.randn(1, 3, 64, 64).clamp(-0.5, 0.5)
>>> image.shape
torch.Size([1, 3, 64, 64])
>>> composed_domain.send(image).shape
torch.Size([1, 3, 64, 64])
>>> print(composed_domain.send(image).min().item(), composed_domain.send(image).max().item())
0.0 1.0
"""

def __init__(self, domains: Iterable[Domain]) -> None:
super().__init__()
self.domains = nn.ModuleList(domains)

def send(self, x: _T) -> _T:
for domain in reversed(self.domains):
x = domain.send(x)
return x

def receive(self, x: _T) -> _T:
for domain in self.domains:
x = domain.receive(x)
return x


class KeyValueDomain(Domain, Generic[_T]):
"""The domain which converts key-value pairs.

This class is used to convert key-value pairs between each domain and library's
internal common space.

Parameters
----------
domain_mapper : dict[str, Domain]
Dictionary that maps keys to domains.
"""

def __init__(self, domain_mapper: dict[str, Domain]) -> None:
super().__init__()
self.domain_mapper = domain_mapper

def send(self, x: dict[str, _T]) -> dict[str, _T]:
return {
key: self.domain_mapper[key].send(value) for key, value in x.items()
}

def receive(self, x: dict[str, _T]) -> dict[str, _T]:
return {
key: self.domain_mapper[key].receive(value) for key, value in x.items()
}
Loading