1+ import abc
2+ from typing import Union , overload
3+
14import numpy as np
5+ import numpy .typing as npt
26from scipy import sparse
37
48from .. import dataset
59
10+ import sys
11+ if sys .version_info < (3 , 11 ):
12+ from typing_extensions import Self
13+ else :
14+ from typing import Self
15+
616import warnings
17+
718warnings .simplefilter ('ignore' , sparse .SparseEfficiencyWarning )
819
9- class Mapper :
20+ MapperShape = Union [npt .NDArray [np .integer ], tuple [int , int , int ]]
21+
22+ class Mapper (abc .ABC ):
1023 '''Maps data from epi volume onto surface using various projections'''
11- def __init__ (self , left , right , shape , subject , xfmname ):
24+ def __init__ (self , left : sparse . csr_matrix , right : sparse . csr_matrix , shape : MapperShape , subject : str , xfmname : str ):
1225 self .idxmap = None
1326 self .masks = [left , right ]
1427 self .nverts = left .shape [0 ] + right .shape [0 ]
@@ -17,7 +30,7 @@ def __init__(self, left, right, shape, subject, xfmname):
1730 self .xfmname = xfmname
1831
1932 @classmethod
20- def from_cache (cls , cachefile , subject , xfmname ) :
33+ def from_cache (cls , cachefile : str , subject : str , xfmname : str ) -> Self :
2134 npz = np .load (cachefile )
2235 left = (npz ['left_data' ], npz ['left_indices' ], npz ['left_indptr' ])
2336 right = (npz ['right_data' ], npz ['right_indices' ], npz ['right_indptr' ])
@@ -26,20 +39,26 @@ def from_cache(cls, cachefile, subject, xfmname):
2639 return cls (lsparse , rsparse , npz ['shape' ], subject , xfmname )
2740
2841 @property
29- def mask (self ):
42+ def mask (self ) -> npt . NDArray [ np . bool_ ] :
3043 mask = np .array (self .masks [0 ].sum (0 ) + self .masks [1 ].sum (0 ))
3144 return (mask .squeeze () != 0 ).reshape (self .shape )
3245
3346 @property
34- def hemimasks (self ):
47+ def hemimasks (self ) -> list [ npt . NDArray [ np . bool_ ]] :
3548 func = lambda m : (np .array (m .sum (0 )).squeeze () != 0 ).reshape (self .shape )
3649 return [func (x ) for x in self .masks ]
3750
3851 def __repr__ (self ):
3952 ptype = self .__class__ .__name__
4053 return '<%s mapper with %d vertices>' % (ptype , self .nverts )
4154
42- def __call__ (self , data ):
55+ @overload
56+ def __call__ (self , data : Union [dataset .Volume , tuple ]) -> dataset .Vertex : ...
57+
58+ @overload
59+ def __call__ (self , data : dataset .Vertex ) -> tuple [npt .NDArray , npt .NDArray ]: ...
60+
61+ def __call__ (self , data : Union [dataset .Vertex , dataset .Volume , tuple ]) -> Union [tuple [npt .NDArray , npt .NDArray ], dataset .Vertex ]:
4362 if isinstance (data , tuple ):
4463 data = dataset .Volume (* data )
4564
@@ -61,17 +80,23 @@ def __call__(self, data):
6180 volume .shape = len (volume ), - 1
6281 volume = volume .T
6382
64- mapped = []
83+ mapped : list [ npt . NDArray ] = []
6584 for mask in self .masks :
66- mapped .append (np .array (mask * volume ).T )
85+ mapped .append (np .array (mask * volume ).T ) # change to @ matmul
6786
6887 if self .idxmap is not None :
6988 mapped [0 ] = mapped [0 ][:, self .idxmap [0 ]]
7089 mapped [1 ] = mapped [1 ][:, self .idxmap [1 ]]
7190
7291 return dataset .Vertex (np .hstack (mapped ).squeeze (), data .subject )
7392
74- def backwards (self , vertexdata ):
93+ @overload
94+ def backwards (self , vertexdata : dataset .Vertex ) -> dataset .Volume : ...
95+
96+ @overload
97+ def backwards (self , vertexdata : npt .NDArray ) -> npt .NDArray : ...
98+
99+ def backwards (self , vertexdata : Union [dataset .Vertex , npt .NDArray ]) -> Union [dataset .Volume , npt .NDArray ]:
75100 '''Projects vertex data back into volume space.
76101
77102 Parameters
@@ -81,8 +106,7 @@ def backwards(self, vertexdata):
81106 If Vertex object is provided, a Volume object is returned
82107 If an array is provided, an array is returned
83108 '''
84- Vert2Vol = isinstance (vertexdata , dataset .Vertex )
85- if Vert2Vol :
109+ if isinstance (vertexdata , dataset .Vertex ):
86110 to_map = vertexdata .data
87111 else :
88112 to_map = vertexdata
@@ -91,8 +115,8 @@ def backwards(self, vertexdata):
91115 # dot the vertex data with the stacked mappers
92116 partial_vertex = bothmappers .T .dot (to_map )
93117 # solve the inverse mapping problem
94- voxeldata = self ._get_backmapper ().solve (partial_vertex ).reshape (self .shape )
95- if Vert2Vol :
118+ voxeldata : npt . NDArray = self ._get_backmapper ().solve (partial_vertex ).reshape (self .shape )
119+ if isinstance ( vertexdata , dataset . Vertex ) :
96120 # construct a volume object with the new data
97121 return dataset .Volume (voxeldata , self .subject , self .xfmname )
98122 else :
@@ -112,10 +136,10 @@ def _get_backmapper(self):
112136 return self ._backmapper
113137
114138 @classmethod
115- def _cache (cls , filename , subject , xfmname , ** kwargs ):
139+ def _cache (cls , filename : str , subject : str , xfmname : str , ** kwargs ) -> Self :
116140 print ('Caching mapper...' )
117141 from ..database import db
118- masks = []
142+ masks : list [ sparse . csr_matrix ] = []
119143 xfm = db .get_xfm (subject , xfmname , xfmtype = 'coord' )
120144 fid = db .get_surf (subject , 'fiducial' , merge = False , nudge = False )
121145
@@ -130,7 +154,19 @@ def _cache(cls, filename, subject, xfmname, **kwargs):
130154 _savecache (filename , masks [0 ], masks [1 ], xfm .shape )
131155 return cls (masks [0 ], masks [1 ], xfm .shape , subject , xfmname )
132156
133- def _savecache (filename , left , right , shape ):
157+ @classmethod
158+ @abc .abstractmethod
159+ def _getmask (cls , coords : npt .NDArray [np .floating ], polys : npt .NDArray [np .integer ], shape : tuple [int , int , int ], ** kwargs ) -> sparse .csr_matrix :
160+ '''Generates a sparse matrix mapping from volume to surface vertices'''
161+ pass
162+
163+ @staticmethod
164+ @abc .abstractmethod
165+ def sampler (coords : npt .NDArray [np .floating ], shape : tuple [int , int , int ], ** kwargs ) -> tuple [npt .NDArray [np .intp ], npt .NDArray [np .intp ], npt .NDArray [np .floating ]]:
166+ '''Generates a sparse matrix mapping from volume to surface vertices'''
167+ pass
168+
169+ def _savecache (filename : str , left : sparse .csr_matrix , right : sparse .csr_matrix , shape : MapperShape ) -> None :
134170 np .savez (filename ,
135171 left_data = left .data ,
136172 left_indices = left .indices ,
0 commit comments