22Provides a LinearOperator for the 2D and 3D curvelet transforms.
33"""
44
5- __version__ = "0.1 "
5+ __version__ = "0.2 "
66__author__ = "Carlos Alberto da Costa Filho"
77
88from itertools import product
9-
9+ from typing import List , Optional , Tuple , Callable , Union , Sequence
1010import numpy as np
11+ from numpy .typing import DTypeLike , NDArray
12+ from numpy .core .multiarray import normalize_axis_index # type: ignore
1113from pylops import LinearOperator
1214
13- from .fdct2d_wrapper import *
14- from .fdct3d_wrapper import *
15+ from .fdct2d_wrapper import * # noqa: F403
16+ from .fdct3d_wrapper import * # noqa: F403
17+
18+ InputDimsLike = Union [Sequence [int ], NDArray [np .int_ ]]
19+ FDCTStructLike = List [List [NDArray ]]
1520
1621
17- def _fdct_docs (dimension ) :
22+ def _fdct_docs (dimension : int ) -> str :
1823 if dimension == 2 :
1924 doc = "2D"
2025 elif dimension == 3 :
2126 doc = "3D"
2227 else :
2328 doc = "2D/3D"
2429 return f"""{ doc } dimensional Curvelet operator.
25- Apply { doc } Curvelet Transform along two directions ``dirs `` of a
30+ Apply { doc } Curvelet Transform along two ``axes `` of a
2631 multi-dimensional array of size ``dims``.
2732
2833 Parameters
2934 ----------
3035 dims : :obj:`tuple`
3136 Number of samples for each dimension
32- dirs : :obj:`tuple`, optional
33- Directions along which FDCT is applied
37+ axes : :obj:`tuple`, optional
38+ Axes along which FDCT is applied
3439 nbscales : :obj:`int`, optional
3540 Number of scales (including the coarsest level);
3641 Defaults to ceil(log2(min(input_dims)) - 3).
@@ -60,36 +65,37 @@ class FDCT(LinearOperator):
6065
6166 def __init__ (
6267 self ,
63- dims ,
64- dirs ,
65- nbscales = None ,
66- nbangles_coarse = 16 ,
67- allcurvelets = True ,
68- dtype = "complex128" ,
69- ):
68+ dims : InputDimsLike ,
69+ axes : Tuple [ int , ...] ,
70+ nbscales : Optional [ int ] = None ,
71+ nbangles_coarse : int = 16 ,
72+ allcurvelets : bool = True ,
73+ dtype : DTypeLike = "complex128" ,
74+ ) -> None :
7075 ndim = len (dims )
7176
72- # Ensure directions are between 0, ndim-1
73- dirs = [ np . core . multiarray . normalize_axis_index (d , ndim ) for d in dirs ]
77+ # Ensure axes are between 0, ndim-1
78+ axes = tuple ( normalize_axis_index (d , ndim ) for d in axes )
7479
75- # If input is shaped (100, 200, 300) and dirs = (0, 2)
80+ # If input is shaped (100, 200, 300) and axes = (0, 2)
7681 # then input_shape will be (100, 300)
77- self ._input_shape = list (dims [d ] for d in dirs )
82+ self ._input_shape = list (int ( dims [d ]) for d in axes )
7883 if nbscales is None :
7984 nbscales = int (np .ceil (np .log2 (min (self ._input_shape )) - 3 ))
8085
8186 # Check dimension
82- if len (dirs ) == 2 :
83- self .fdct = fdct2d_forward_wrap
84- self .ifdct = fdct2d_inverse_wrap
85- _ , _ , _ , _ , nxs , nys = fdct2d_param_wrap (
87+ sizes : Union [Tuple [NDArray , NDArray ], Tuple [NDArray , NDArray , NDArray ]]
88+ if len (axes ) == 2 :
89+ self .fdct : Callable = fdct2d_forward_wrap # type: ignore # noqa: F405
90+ self .ifdct : Callable = fdct2d_inverse_wrap # type: ignore # noqa: F405
91+ _ , _ , _ , _ , nxs , nys = fdct2d_param_wrap ( # type: ignore # noqa: F405
8692 * self ._input_shape , nbscales , nbangles_coarse , allcurvelets
8793 )
8894 sizes = (nys , nxs )
89- elif len (dirs ) == 3 :
90- self .fdct = fdct3d_forward_wrap
91- self .ifdct = fdct3d_inverse_wrap
92- _ , _ , _ , nxs , nys , nzs = fdct3d_param_wrap (
95+ elif len (axes ) == 3 :
96+ self .fdct : Callable = fdct3d_forward_wrap # type: ignore # noqa: F405
97+ self .ifdct : Callable = fdct3d_inverse_wrap # type: ignore # noqa: F405
98+ _ , _ , _ , nxs , nys , nzs = fdct3d_param_wrap ( # type: ignore # noqa: F405
9399 * self ._input_shape , nbscales , nbangles_coarse , allcurvelets
94100 )
95101 sizes = (nzs , nys , nxs )
@@ -105,10 +111,11 @@ def __init__(
105111 raise NotImplementedError ("Only complex types supported" )
106112
107113 # Now we need to build the iterator which will only iterate along
108- # the required directions . Following the example above,
114+ # the required axes . Following the example above,
109115 # iterable_axes = [ False, True, False ]
110- iterable_axes = [False if i in dirs else True for i in range (ndim )]
111- self ._ndim_iterable = np .prod (np .array (dims )[iterable_axes ])
116+ iterable_axes = [i not in axes for i in range (ndim )]
117+ iterable_dims = np .array (dims )[iterable_axes ]
118+ self ._ndim_iterable = np .prod (iterable_dims )
112119
113120 # Build the iterator itself. In our example, the slices
114121 # would be [:, i, :] for i in range(200)
@@ -134,23 +141,27 @@ def __init__(
134141 self ._output_len = sum (np .prod (j ) for i in self .shapes for j in i )
135142
136143 # Save some useful properties
137- self .dims = dims
138- self .dirs = dirs
144+ self .inpdims = dims
145+ self .axes = axes
139146 self .nbscales = nbscales
140147 self .nbangles_coarse = nbangles_coarse
141148 self .allcurvelets = allcurvelets
142149 self .cpx = cpx
143150
144151 # Required by PyLops
145- self .shape = (self ._ndim_iterable * self ._output_len , np .prod (dims ))
146- self .dtype = dtype
147- self .explicit = False
152+ super ().__init__ (
153+ dtype = dtype ,
154+ dims = self .inpdims ,
155+ dimsd = (* iterable_dims , self ._output_len ),
156+ )
148157
149- def _matvec (self , x ):
150- fwd_out = np .zeros ((self ._output_len , self ._ndim_iterable ), dtype = self .dtype )
158+ def _matvec (self , x : NDArray ) -> NDArray :
159+ fwd_out = np .zeros (
160+ (self ._output_len , self ._ndim_iterable ), dtype = self .dtype
161+ )
151162 for i , index in enumerate (self ._iterator ):
152- x_shaped = np .array (x .reshape (self .dims )[index ])
153- c_struct = self .fdct (
163+ x_shaped = np .array (x .reshape (self .inpdims )[index ])
164+ c_struct : FDCTStructLike = self .fdct (
154165 self .nbscales ,
155166 self .nbangles_coarse ,
156167 self .allcurvelets ,
@@ -159,12 +170,12 @@ def _matvec(self, x):
159170 fwd_out [:, i ] = self .vect (c_struct )
160171 return fwd_out .ravel ()
161172
162- def _rmatvec (self , y ) :
173+ def _rmatvec (self , y : NDArray ) -> NDArray :
163174 y_shaped = y .reshape (self ._output_len , self ._ndim_iterable )
164- inv_out = np .zeros (self .dims , dtype = self .dtype )
175+ inv_out = np .zeros (self .inpdims , dtype = self .dtype )
165176 for i , index in enumerate (self ._iterator ):
166177 y_struct = self .struct (np .array (y_shaped [:, i ]))
167- xinv = self .ifdct (
178+ xinv : NDArray = self .ifdct (
168179 * self ._input_shape ,
169180 self .nbscales ,
170181 self .nbangles_coarse ,
@@ -175,10 +186,10 @@ def _rmatvec(self, y):
175186
176187 return inv_out .ravel ()
177188
178- def inverse (self , x ) :
189+ def inverse (self , x : NDArray ) -> NDArray :
179190 return self ._rmatvec (x )
180191
181- def struct (self , x ) :
192+ def struct (self , x : NDArray ) -> FDCTStructLike :
182193 c_struct = []
183194 k = 0
184195 for i in range (len (self .shapes )):
@@ -190,7 +201,7 @@ def struct(self, x):
190201 c_struct .append (angles )
191202 return c_struct
192203
193- def vect (self , x ) :
204+ def vect (self , x : FDCTStructLike ) -> NDArray :
194205 return np .concatenate ([coef .ravel () for angle in x for coef in angle ])
195206
196207
@@ -199,30 +210,34 @@ class FDCT2D(FDCT):
199210
200211 def __init__ (
201212 self ,
202- dims ,
203- dirs = (- 2 , - 1 ),
204- nbscales = None ,
205- nbangles_coarse = 16 ,
206- allcurvelets = True ,
207- dtype = "complex128" ,
208- ):
209- if len (dirs ) != 2 :
210- raise ValueError ("FDCT2D must be called with exactly two directions" )
211- super ().__init__ (dims , dirs , nbscales , nbangles_coarse , allcurvelets , dtype )
213+ dims : InputDimsLike ,
214+ axes : Tuple [int , ...] = (- 2 , - 1 ),
215+ nbscales : Optional [int ] = None ,
216+ nbangles_coarse : int = 16 ,
217+ allcurvelets : bool = True ,
218+ dtype : DTypeLike = "complex128" ,
219+ ) -> None :
220+ if len (axes ) != 2 :
221+ raise ValueError ("FDCT2D must be called with exactly two axes" )
222+ super ().__init__ (
223+ dims , axes , nbscales , nbangles_coarse , allcurvelets , dtype
224+ )
212225
213226
214227class FDCT3D (FDCT ):
215228 __doc__ = _fdct_docs (3 )
216229
217230 def __init__ (
218231 self ,
219- dims ,
220- dirs = (- 3 , - 2 , - 1 ),
221- nbscales = None ,
222- nbangles_coarse = 16 ,
223- allcurvelets = True ,
224- dtype = "complex128" ,
225- ):
226- if len (dirs ) != 3 :
227- raise ValueError ("FDCT3D must be called with exactly three directions" )
228- super ().__init__ (dims , dirs , nbscales , nbangles_coarse , allcurvelets , dtype )
232+ dims : InputDimsLike ,
233+ axes : Tuple [int , ...] = (- 3 , - 2 , - 1 ),
234+ nbscales : Optional [int ] = None ,
235+ nbangles_coarse : int = 16 ,
236+ allcurvelets : bool = True ,
237+ dtype : DTypeLike = "complex128" ,
238+ ) -> None :
239+ if len (axes ) != 3 :
240+ raise ValueError ("FDCT3D must be called with exactly three axes" )
241+ super ().__init__ (
242+ dims , axes , nbscales , nbangles_coarse , allcurvelets , dtype
243+ )
0 commit comments