Skip to content

Commit 99680f5

Browse files
authored
Updates to 0.2 (#7)
* feature: update to pylops version 2.0 * linting and annotation * fix self.fdct annotation * idiomatic iterable_axes * auxiliary iterable_dims * improve merge * forgot import * improve plot * newline * update version * update example with pylops 2.0 style * update pylops dependency * new example: visualize sigmoid curvelets * feature: new notebook * simplify plotting
1 parent 1b7cb02 commit 99680f5

13 files changed

Lines changed: 936 additions & 246 deletions

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Python wrapper for [CurveLab](http://www.curvelet.org)'s 2D and 3D curvelet tran
44

55
## Installation
66

7-
Installing `curvelops` requires the following components:
7+
Installing `curvelops` requires the following external components:
88

99
- [FFTW](http://www.fftw.org/download.html) 2.1.5
1010
- [CurveLab](http://curvelet.org/software.html) >= 2.0.2
@@ -28,11 +28,11 @@ For a 2D transform, you can get started with:
2828
import numpy as np
2929
import curvelops as cl
3030

31-
x = np.random.normal(0., 1., (100, 50))
32-
FDCT = cl.FDCT2D(dims=(100, 50))
33-
c = FDCT * x.ravel()
34-
xinv = FDCT.H * c
35-
assert np.allclose(x, xinv.reshape(100, 50))
31+
x = np.random.randn(100, 50)
32+
FDCT = cl.FDCT2D(dims=x.shape)
33+
c = FDCT @ x
34+
xinv = FDCT.H @ c
35+
np.testing.assert_allclose(x, xinv)
3636
```
3737

3838
An excellent place to see how to use the library is the `examples/` folder. `Demo_Single_Curvelet` for example contains a `curvelops` version of the CurveLab Matlab demo.
@@ -54,7 +54,7 @@ The `--prefix` and `--with-gcc` are optional and determine where it will install
5454

5555
### CurveLab
5656

57-
In the file `makefile.opt` set `FFTW_DIR`, `CC` and `CXX` variables as required in the instructions. To keep things consistent, set `FFTW_DIR=/home/user/opt/fftw-2.1.5` (or whatever directory was used in the `--prefix` option). For the others, use the same compiler which was used to compile FFTW.
57+
In the file `makefile.opt` set `FFTW_DIR`, `CC` and `CXX` variables as required in the instructions. To keep things consistent, set `FFTW_DIR=/home/user/opt/fftw-2.1.5` (or whatever directory was used in the `--prefix` option above). For the other options (`CC` and `CXX`), use the same compiler which was used to compile FFTW.
5858

5959
### curvelops
6060

curvelops/curvelops.py

Lines changed: 80 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,40 @@
22
Provides 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

88
from itertools import product
9-
9+
from typing import List, Optional, Tuple, Callable, Union, Sequence
1010
import numpy as np
11+
from numpy.typing import DTypeLike, NDArray
12+
from numpy.core.multiarray import normalize_axis_index # type: ignore
1113
from 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

214227
class 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+
)

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
# The short X.Y version
2727
version = ""
2828
# The full version, including alpha/beta/rc tags
29-
release = "0.1"
29+
release = "0.2"
3030

3131

3232
# -- General configuration ---------------------------------------------------

docs/source/static/demo.png

-88.8 KB
Loading

0 commit comments

Comments
 (0)