Skip to content

Commit eee492c

Browse files
committed
TimeSeriesVolume and assignment
1 parent c7310d2 commit eee492c

3 files changed

Lines changed: 82 additions & 3 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import numpy as np
2+
import nibabel
3+
import siibra
4+
5+
6+
def create_synthetic_data():
7+
julich_pmaps = siibra.get_map(
8+
parcellation="julich 2.9",
9+
space="mni152",
10+
maptype="statistical"
11+
)
12+
template_img = julich_pmaps.space.get_template().fetch()
13+
arr = np.zeros(list(template_img.shape) + [len(julich_pmaps.volumes)])
14+
for i, img in enumerate(julich_pmaps.fetch_iter()):
15+
arr[:, :, :, i] += img.dataobj
16+
return siibra.volumes.volume.TimeSeriesVolume(
17+
nibabel.nifti1.Nifti1Image(arr, affine=template_img.affine),
18+
time_index=list(range(len(julich_pmaps.volumes)))
19+
)
20+
21+
22+
def test_timeseries_volume_assignment():
23+
difumo64 = siibra.get_map(
24+
parcellation="difumo 64",
25+
space="mni152",
26+
maptype="statistical"
27+
)
28+
synthetic_vol = create_synthetic_data()
29+
assignments = difumo64.assign(synthetic_vol, lower_threshold=0.6, split_components=False)
30+
print(assignments)

siibra/volumes/parcellationmap.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class MapAssignment:
5656
volume: int
5757
fragment: str
5858
map_value: np.ndarray
59+
time_index: Union[int, None]
5960

6061

6162
@dataclass
@@ -957,6 +958,7 @@ def assign(
957958
# format assignments as pandas dataframe
958959
columns = [
959960
"input structure",
961+
"time_index",
960962
"centroid",
961963
"volume",
962964
"fragment",
@@ -970,7 +972,7 @@ def assign(
970972
"input containedness"
971973
]
972974
if len(assignments) == 0:
973-
return pd.DataFrame(columns=columns)
975+
return pd.DataFrame(columns=columns).dropna(axis='columns', how='all')
974976
# determine the unique set of observed indices in order to do region lookups
975977
# only once for each map index occurring in the point list
976978
labelled = self.is_labelled # avoid calling this in a loop
@@ -1214,12 +1216,31 @@ def _assign_volume(
12141216
volume=index.volume,
12151217
fragment=index.fragment,
12161218
map_value=index.label,
1219+
time_index=kwargs.get("time_index", None),
12171220
**asdict(scores)
12181221
)
12191222
)
12201223

12211224
return assignments
12221225

1226+
def _assign_timeseries_volume(
1227+
self,
1228+
queryvolume: "_volume.TimeSeriesVolume",
1229+
lower_threshold: float,
1230+
split_components: bool = True,
1231+
**kwargs
1232+
) -> List[AssignImageResult]:
1233+
assignments = []
1234+
for v_t in siibra_tqdm(queryvolume, unit='time_index'):
1235+
assignments_t = self._assign_volume(
1236+
v_t,
1237+
lower_threshold=lower_threshold,
1238+
split_components=split_components,
1239+
time_index=v_t.time_index,
1240+
**kwargs
1241+
)
1242+
assignments.extend(assignments_t)
1243+
12231244

12241245
def from_volume(
12251246
name: str,

siibra/volumes/volume.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# limitations under the License.
1515
"""A specific mesh or 3D array."""
1616

17-
from typing import List, Dict, Union, Set, TYPE_CHECKING
17+
from typing import Iterable, List, Dict, Union, Set, TYPE_CHECKING
1818
from dataclasses import dataclass
1919
from time import sleep
2020
import json
@@ -633,6 +633,7 @@ def __init__(
633633
label: int = None,
634634
fragment: str = None,
635635
threshold: float = None,
636+
time_index: int = None,
636637
):
637638
"""
638639
A prescribed Volume to fetch specified label and fragment.
@@ -647,6 +648,8 @@ def __init__(
647648
If a volume is fragmented, get a specified one.
648649
threshold : float, default None
649650
Provide a float value to threshold the image.
651+
time_index: int = None,
652+
If parent volume is a timeseries Nifti, filter a time index without fetching the full image.
650653
"""
651654
name = parent_volume.name
652655
if label:
@@ -655,6 +658,8 @@ def __init__(
655658
name += f" - fragment: {fragment}"
656659
if threshold:
657660
name += f" - threshold: {threshold}"
661+
if time_index:
662+
name += f" - time index: {time_index}"
658663
Volume.__init__(
659664
self,
660665
space_spec=parent_volume._space_spec,
@@ -664,6 +669,7 @@ def __init__(
664669
self.fragment = fragment
665670
self.label = label
666671
self.threshold = threshold
672+
self.time_index = time_index
667673

668674
def fetch(
669675
self,
@@ -680,7 +686,8 @@ def fetch(
680686
kwargs["label"] = self.label
681687

682688
result = super().fetch(format=format, **kwargs)
683-
689+
if self.time_index is not None:
690+
result = result.slice[:, :, :, self.time_index]
684691
if self.threshold is not None:
685692
assert self.label is None
686693
if not isinstance(result, Nifti1Image):
@@ -710,6 +717,27 @@ def get_boundingbox(
710717
)
711718

712719

720+
class TimeSeriesVolume(Volume):
721+
def __init__(
722+
self,
723+
time_index: Union[np.ndarray, None] = None,
724+
):
725+
self.time_index = time_index
726+
727+
def __iter__(self) -> Iterable[FilteredVolume]:
728+
yield from (
729+
FilteredVolume(parent_volume=self, time_index=t)
730+
for t in self.time_index
731+
)
732+
733+
def get_index(self, time_index: int):
734+
return FilteredVolume(parent_volume=self, time_index=time_index)
735+
736+
def fetch(self, format: str = None, time_index: int = None, **kwargs):
737+
img = super().fetch(format, **kwargs)
738+
return img.slice[:, :, :, time_index] if time_index else img
739+
740+
713741
class ReducedVolume(Volume):
714742
def __init__(
715743
self,

0 commit comments

Comments
 (0)