tomwer 1.2.0a3__py3-none-any.whl → 1.2.0a4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,387 +0,0 @@
1
- #!/usr/bin/env python
2
- # -*- coding: utf-8 -*-
3
-
4
- import numpy as np
5
- from math import pi
6
- from bisect import bisect
7
- from ..utils import generate_powers
8
- from silx.utils.enum import Enum as _Enum
9
-
10
-
11
- def lmicron_to_db(Lmicron, energy, distance):
12
- """
13
- Utility to convert the "Lmicron" parameter of PyHST
14
- to a value of delta/beta.
15
-
16
- Parameters
17
- ----------
18
- Lmicron: float
19
- Length in microns, values of the parameter "PAGANIN_Lmicron"
20
- in PyHST2 parameter file.
21
- energy: float
22
- Energy in keV.
23
- distance: float
24
- Sample-detector distance in microns
25
-
26
- Formula
27
- -------
28
- The conversion is done using the formula
29
-
30
- $$
31
- L^2 = \pi \lambda D \frac{\delta}{\beta} # noqa W605
32
- $$
33
- The PyHST2 normalization differs from the one used by other softwares
34
- like tomopy by a factor $1/(4\pi^2)$
35
- """
36
- L2 = Lmicron**2
37
- wavelength = 1.23984199e-3 / energy
38
- return L2 / (pi * wavelength * distance)
39
-
40
-
41
- class PaddingMode(_Enum):
42
- ZEROS = "zeros"
43
- MEAN = "mean"
44
- EDGE = "edge"
45
- SYMMETRIC = "symmetric"
46
- REFLECT = "reflect"
47
-
48
-
49
- class PaganinPhaseRetrieval(object):
50
- """
51
- Paganin Phase Retrieval for an infinitely distant point source.
52
- Formula (10) in [1].
53
-
54
- Parameters
55
- ----------
56
- shape: int or tuple
57
- Shape of each radio, in the format (num_rows, num_columns), i.e
58
- (size_vertical, size_horizontal).
59
- If an integer is provided, the shape is assumed to be square.
60
- distance : float, optional
61
- Propagation distance in cm.
62
- energy : float, optional
63
- Energy in keV.
64
- delta_beta: float, optional
65
- delta/beta ratio, where n = (1 - delta) + i*beta is the complex
66
- refractive index of the sample.
67
- pixel_size : float, optional
68
- Detector pixel size in microns.
69
- padding : str, optional
70
- Padding method. Available are "zeros", "mean", "edge", "sym",
71
- "reflect". Default is "edge".
72
- Please refer to the "Padding" section below for more details.
73
- margin: tuple, optional
74
- The user may provide integers values U, D, L, R as a tuple under the
75
- form ((U, D), (L, R)) (same syntax as numpy.pad()).
76
- The resulting filtered radio will have a size equal to
77
- (size_vertic - U - D, size_horiz - L - R).
78
- These values serve to create a "margin" for the filtering process,
79
- where U, D, L R are the margin of the Up, Down, Left and Right part,
80
- respectively.
81
- The filtering is done on a subset of the input radio. The subset
82
- size is (Nrows - U - D, Ncols - R - L).
83
- The margins is used to do the padding for the rest of the padded
84
- array.
85
-
86
- For example in one dimension, where padding="edge":
87
-
88
- <------------------------------ padded_size --------------------------->
89
- [padding=edge | padding=data | radio data | padding=data | padding=edge]
90
- <------ N2 ---><----- L -----><- (N-L-R)--><----- R -----><----- N2 --->
91
-
92
- Some or all the values U, D, L, R can be 0. In this case,
93
- the padding of the parts related to the zero values will
94
- fall back to the one of "padding" parameter.
95
- For example, if padding="edge" and L, R are 0, then
96
- the left and right parts will be padded with the edges, while
97
- the Up and Down parts will be padded using the the user-provided
98
- margins of the radio, and the final data will have shape
99
- (Nrows - U - D, Ncols).
100
- Some or all the values U, D, L, R can be the string "auto".
101
- In this case, the values of U, D, L, R are automatically computed
102
- as a function of the Paganin filter width.
103
- use_R2C: bool, optional
104
- Whether to use Real-to-Complex (R2C) transform instead of
105
- standard Complex-to-Complex transform, providing better performances
106
-
107
- Padding methods
108
- ---------------
109
- The phase retrieval is a convolution done in Fourier domain using FFT,
110
- so the Fourier transform size has to be at least twice the size of
111
- the original data. Mathematically, the data should be padded with zeros
112
- before being Fourier transformed. However, in practice, this can lead
113
- to artefacts at the edges (Gibbs effect) if the data does not go to
114
- zero at the edges.
115
- Apart from applying an apodization (Hamming, Blackman, etc), a common
116
- strategy to avoid these artefacts is to pad the data.
117
- In tomography reconstruction, this is usually done by replicating the
118
- last(s) value(s) of the edges ; but one can think of other methods:
119
-
120
- - "zeros": the data is simply padded with zeros.
121
- - "mean": the upper side of extended data is padded with the mean of
122
- the first row, the lower side with the mean of the last row, etc.
123
- - "edge": the data is padded by replicating the edges.
124
- This is the default mode.
125
- - "sym": the data is padded by mirroring the data with respect
126
- to its edges. See numpy.pad().
127
- - "reflect": the data is padded by reflecting the data with respect
128
- to its edges, including the edges. See numpy.pad().
129
-
130
-
131
- Formulas
132
- --------
133
- The radio is divided, in the Fourier domain, by the original
134
- "Paganin filter" [1]
135
-
136
- $$
137
- F + 1 + \frac{\delta}{\beta} \lambda D \rho |k|^2 # noqa W605
138
- $$
139
- where $k$ is the wave vector, computed as
140
-
141
- $$
142
- k_l = \frac{1}{P} (\frac{-1}{2} + \frac{l}{N-1})
143
- $$
144
- where $P$ is the pixel size, $N$ the number of pixels in one direction,
145
- and $l \in [0, N-1]$. # noqa W605
146
- The factor $\rho$ is either $\pi$ or $1/(4\pi^2)$ # noqa W605
147
- depending on the convention (default is the former).
148
-
149
-
150
- References
151
- -----------
152
- [1] D. Paganin Et Al, "Simultaneous phase and amplitude extraction
153
- from a single defocused image of a homogeneous object",
154
- Journal of Microscopy, Vol 206, Part 1, 2002
155
- """
156
-
157
- powers = generate_powers()
158
-
159
- def __init__(
160
- self,
161
- shape,
162
- distance=50,
163
- energy=20,
164
- delta_beta=250.0,
165
- pixel_size=1,
166
- padding="edge",
167
- margin=None,
168
- use_R2C=True,
169
- ):
170
- self._init_parameters(
171
- distance, energy, pixel_size, delta_beta, padding, use_R2C
172
- )
173
- self._calc_shape(shape, margin)
174
- self.compute_filter()
175
-
176
- def _init_parameters(
177
- self, distance, energy, pixel_size, delta_beta, padding, use_R2C
178
- ):
179
- self.distance_cm = distance
180
- self.distance_micron = distance * 1e4
181
- self.energy_kev = energy
182
- self.pixel_size_micron = pixel_size
183
- self.delta_beta = delta_beta
184
- self.wavelength_micron = 1.23984199e-3 / self.energy_kev
185
- self.padding = padding
186
- self.padding_methods = {
187
- PaddingMode.ZEROS: self._pad_zeros,
188
- PaddingMode.MEAN: self._pad_mean,
189
- PaddingMode.EDGE: self._pad_edge,
190
- PaddingMode.SYMMETRIC: self._pad_sym,
191
- PaddingMode.REFLECT: self._pad_reflect,
192
- }
193
- self.use_R2C = use_R2C
194
- if use_R2C:
195
- self.fft_func = np.fft.rfft2
196
- self.ifft_func = np.fft.irfft2
197
- else:
198
- self.fft_func = np.fft.fft2
199
- self.ifft_func = np.fft.ifft2
200
-
201
- def _calc_shape(self, shape, margin):
202
- if np.isscalar(shape):
203
- shape = (shape, shape)
204
- else:
205
- assert len(shape) == 2
206
- self.shape = shape
207
- self._set_margin_value(margin)
208
- self._calc_padded_shape()
209
-
210
- def _set_margin_value(self, margin):
211
- self.margin = margin
212
- if margin is None:
213
- self.shape_inner = self.shape
214
- self.use_margin = False
215
- self.margin = ((0, 0), (0, 0))
216
- return
217
- self.use_margin = True
218
- try:
219
- ((U, D), (L, R)) = margin
220
- except ValueError:
221
- raise ValueError("Expected margin in the format ((U, D), (L, R))")
222
- for val in [U, D, L, R]:
223
- if type(val) is str and val != "auto":
224
- raise ValueError("Expected either an integer, or 'auto'")
225
- if int(val) != val or val < 0:
226
- raise ValueError("Expected positive integers for margin values")
227
- self.shape_inner = (self.shape[0] - U - D, self.shape[1] - L - R)
228
-
229
- def _calc_padded_shape(self):
230
- """
231
- Compute the padded shape.
232
- If margin = 0, length_padded = next_power(2*length).
233
- Otherwise : length_padded = next_power(2*(length - margins))
234
-
235
- Principle
236
- ----------
237
-
238
- <--------------------- nx_p --------------------->
239
- | | original data | |
240
- < -- Pl - ><-- L -->< -- nx --><-- R --><-- Pr -->
241
- <----------- nx0 ----------->
242
-
243
- Pl, Pr : left/right padding length
244
- L, R : left/right margin
245
- nx : length of inner data (and length of final result)
246
- nx0 : length of original data
247
- nx_p : total length of padded data
248
- """
249
- n_y, n_x = self.shape_inner
250
- n_y_p = self._get_next_power(2 * n_y)
251
- n_x_p = self._get_next_power(2 * n_x)
252
- self.shape_padded = (n_y_p, n_x_p)
253
- self.data_padded = np.zeros((n_y_p, n_x_p), dtype=np.float64)
254
-
255
- ((U, D), (L, R)) = self.margin
256
- n_y0, n_x0 = self.shape
257
- self.pad_top_len = (n_y_p - n_y0) // 2
258
- self.pad_bottom_len = n_y_p - n_y0 - self.pad_top_len
259
- self.pad_left_len = (n_x_p - n_x0) // 2
260
- self.pad_right_len = n_x_p - n_x0 - self.pad_left_len
261
-
262
- def _get_next_power(self, n):
263
- """
264
- Given a number, get the closest (upper) number p such that
265
- p is a power of 2, 3, 5 and 7.
266
- """
267
- idx = bisect(self.powers, n)
268
- if self.powers[idx - 1] == n:
269
- return n
270
- return self.powers[idx]
271
-
272
- def compute_filter(self):
273
- nyp, nxp = self.shape_padded
274
- fftfreq = np.fft.rfftfreq if self.use_R2C else np.fft.fftfreq
275
- fy = np.fft.fftfreq(nyp, d=self.pixel_size_micron)
276
- fx = fftfreq(nxp, d=self.pixel_size_micron)
277
- self._coords_grid = np.add.outer(fy**2, fx**2)
278
- #
279
- k2 = self._coords_grid
280
- D = self.distance_micron
281
- L = self.wavelength_micron
282
- db = self.delta_beta
283
- self.paganin_filter = 1.0 / (1 + db * L * D * pi * k2) # HST / savu
284
- # ~ self.paganin_filter = 1.0 / (1 + db * L * D * k2/ (4*pi)) # Paganin / tomopy
285
-
286
- def pad_with_values(self, data, top_val=0, bottom_val=0, left_val=0, right_val=0):
287
- """
288
- Pad the data into `self.padded_data` with values.
289
-
290
- Parameters
291
- ----------
292
- data: numpy.ndarray
293
- data (radio)
294
- top_val: float or numpy.ndarray, optional
295
- Value(s) to fill the top of the padded data with.
296
- bottom_val: float or numpy.ndarray, optional
297
- Value(s) to fill the bottom of the padded data with.
298
- left_val: float or numpy.ndarray, optional
299
- Value(s) to fill the left of the padded data with.
300
- right_val: float or numpy.ndarray, optional
301
- Value(s) to fill the right of the padded data with.
302
- """
303
- self.data_padded.fill(0)
304
- Pu, Pd = self.pad_top_len, self.pad_bottom_len
305
- Pl, Pr = self.pad_left_len, self.pad_right_len
306
- self.data_padded[:Pu, :] = top_val
307
- self.data_padded[-Pd:, :] = bottom_val
308
- self.data_padded[:, :Pl] = left_val
309
- self.data_padded[:, -Pr:] = right_val
310
- self.data_padded[Pu:-Pd, Pl:-Pr] = data
311
- # Transform the data to the FFT layout
312
- self.data_padded = np.roll(self.data_padded, (-Pu, -Pl), axis=(0, 1))
313
-
314
- def _pad_zeros(self, data):
315
- return self.pad_with_values(
316
- data, top_val=0, bottom_val=0, left_val=0, right_val=0
317
- )
318
-
319
- def _pad_mean(self, data):
320
- """
321
- Pad the data at each border with a different constant value.
322
- The value depends on the padding size:
323
- - On the left, value = mean(first data column)
324
- - On the right, value = mean(last data column)
325
- - On the top, value = mean(first data row)
326
- - On the bottom, value = mean(last data row)
327
- """
328
- return self.pad_with_values(
329
- data,
330
- top_val=np.mean(data[0, :]),
331
- bottom_val=np.mean(data[-1, :]),
332
- left_val=np.mean(data[:, 0]),
333
- right_val=np.mean(data[:, -1]),
334
- )
335
-
336
- def _pad_numpy(self, data, mode):
337
- data_padded = np.pad(
338
- data,
339
- (
340
- (self.pad_top_len, self.pad_bottom_len),
341
- (self.pad_left_len, self.pad_right_len),
342
- ),
343
- mode=mode.value,
344
- )
345
- # Transform the data to the FFT layout
346
- Pu, Pl = self.pad_top_len, self.pad_left_len
347
- return np.roll(data_padded, (-Pu, -Pl), axis=(0, 1))
348
-
349
- def _pad_edge(self, data):
350
- self.data_padded = self._pad_numpy(data, mode=PaddingMode.EDGE)
351
-
352
- def _pad_sym(self, data):
353
- self.data_padded = self._pad_numpy(data, mode=PaddingMode.SYMMETRIC)
354
-
355
- def _pad_reflect(self, data):
356
- self.data_padded = self._pad_numpy(data, mode=PaddingMode.REFLECT)
357
-
358
- def pad_data(self, data, padding_method=None):
359
- padding_method = padding_method or self.padding
360
- padding_method = PaddingMode.from_value(padding_method)
361
- if padding_method not in self.padding_methods:
362
- raise ValueError(
363
- "Unknown padding method %s. Available are: %s"
364
- % (padding_method, str(list(self.padding_methods.keys())))
365
- )
366
- pad_func = self.padding_methods[padding_method]
367
- pad_func(data)
368
- return self.data_padded
369
-
370
- def apply_filter(self, radio, padding_method=None):
371
- self.pad_data(radio, padding_method=padding_method)
372
- radio_f = self.fft_func(self.data_padded)
373
- radio_f *= self.paganin_filter
374
- radio_filtered = self.ifft_func(radio_f).real
375
- s0, s1 = self.shape_inner
376
- ((U, _), (L, _)) = self.margin
377
- return radio_filtered[U : U + s0, L : L + s1]
378
-
379
- def lmicron_to_db(self, Lmicron):
380
- """
381
- Utility to convert the "Lmicron" parameter of PyHST
382
- to a value of delta/beta.
383
- Please see the doc of nabu.preproc.phase.lmicron_to_db()
384
- """
385
- return lmicron_to_db(Lmicron, self.energy_kev, self.distance_micron)
386
-
387
- __call__ = apply_filter
@@ -1,201 +0,0 @@
1
- import os
2
- import numpy as np
3
- from time import time
4
- from itertools import product
5
-
6
- _warnings = {}
7
-
8
-
9
- def nextpow2(N):
10
- p = 1
11
- while p < N:
12
- p *= 2
13
- return p
14
-
15
-
16
- def updiv(a, b):
17
- return (a + (b - 1)) // b
18
-
19
-
20
- def get_folder_path(foldername=""):
21
- _file_dir = os.path.dirname(os.path.realpath(__file__))
22
- package_dir = _file_dir
23
- return os.path.join(package_dir, foldername)
24
-
25
-
26
- def get_cuda_srcfile(filename):
27
- src_relpath = os.path.join("cuda", "src")
28
- cuda_src_folder = get_folder_path(foldername=src_relpath)
29
- return os.path.join(cuda_src_folder, filename)
30
-
31
-
32
- def _sizeof(Type):
33
- """
34
- return the size (in bytes) of a scalar type, like the C behavior
35
- """
36
- return np.dtype(Type).itemsize
37
-
38
-
39
- class FFTShift(object):
40
- def __init__(self, N):
41
- self._init_shape(N)
42
-
43
- def _init_shape(self, N):
44
- self.N = N
45
- self.N2 = N // 2
46
- self.N2b = N - self.N2 # N = N2 + N2b
47
-
48
- def fftshift_coord(self, i):
49
- if i < self.N2:
50
- return i + self.N2b
51
- else:
52
- return i - self.N2
53
-
54
- def fftshift_coords(self, coords):
55
- N2 = self.N2
56
- N2b = self.N2b
57
- res = np.zeros_like(coords)
58
- mask = coords < N2
59
- res[:N2] = coords[mask] + N2b
60
- res[N2:] = coords[np.logical_not(mask)] - N2
61
- return res
62
-
63
-
64
- def generate_powers():
65
- """
66
- Generate a list of powers of [2, 3, 5, 7],
67
- up to (2**15)*(3**9)*(5**6)*(7**5).
68
- """
69
- primes = [2, 3, 5, 7]
70
- maxpow = {2: 15, 3: 9, 5: 6, 7: 5}
71
- valuations = []
72
- for prime in primes:
73
- # disallow any odd number (for R2C transform), and any number
74
- # not multiple of 4 (Ram-Lak filter behaves strangely when
75
- # dwidth_padded/2 is not even)
76
- minval = 2 if prime == 2 else 0
77
- valuations.append(range(minval, maxpow[prime] + 1))
78
- powers = product(*valuations)
79
- res = []
80
- for pw in powers:
81
- res.append(np.prod(list(map(lambda x: x[0] ** x[1], zip(primes, pw)))))
82
- return np.unique(res)
83
-
84
-
85
- def calc_padding_lengths1D(length, length_padded):
86
- """
87
- Compute the padding lengths at both side along one dimension.
88
-
89
- Parameters
90
- ----------
91
- length: int
92
- Number of elements along one dimension of the original array
93
- length_padded: tuple
94
- Number of elements along one dimension of the padded array
95
-
96
- Returns
97
- -------
98
- pad_lengths: tuple
99
- A tuple under the form (padding_left, padding_right). These are the
100
- lengths needed to pad the original array.
101
- """
102
- pad_left = (length_padded - length) // 2
103
- pad_right = length_padded - length - pad_left
104
- return (pad_left, pad_right)
105
-
106
-
107
- def calc_padding_lengths(shape, shape_padded):
108
- """
109
- Multi-dimensional version of calc_padding_lengths1D.
110
- Please refer to the documentation of calc_padding_lengths1D.
111
- """
112
- assert len(shape) == len(shape_padded)
113
- padding_lengths = []
114
- for dim_len, dim_len_padded in zip(shape, shape_padded):
115
- pad0, pad1 = calc_padding_lengths1D(dim_len, dim_len_padded)
116
- padding_lengths.append((pad0, pad1))
117
- return tuple(padding_lengths)
118
-
119
-
120
- # ------------------------------------------------------------------------------
121
- # ------------------------ Image (move elsewhere ?) ----------------------------
122
- # ------------------------------------------------------------------------------
123
-
124
-
125
- def generate_coords(img_shp, center=None):
126
- l_r, l_c = float(img_shp[0]), float(img_shp[1])
127
- R, C = np.mgrid[:l_r, :l_c] # np.indices is faster
128
- if center is None:
129
- center0, center1 = l_r / 2.0, l_c / 2.0
130
- else:
131
- center0, center1 = center
132
- R += 0.5 - center0
133
- C += 0.5 - center1
134
- return R, C
135
-
136
-
137
- def clip_circle(img, center=None, radius=None):
138
- R, C = generate_coords(img.shape, center)
139
- M = R**2 + C**2
140
- res = np.zeros_like(img)
141
- res[M < radius**2] = img[M < radius**2]
142
- return res
143
-
144
-
145
- def apply_along_z(vol, func, res):
146
- for i in range(vol.shape[0]):
147
- res[i] = func(vol[i])
148
- return res
149
-
150
-
151
- # ------------------------------------------------------------------------------
152
- # ---------------------------- Decorators --------------------------------------
153
- # ------------------------------------------------------------------------------
154
-
155
-
156
- def measure_time(func):
157
- def wrapper(*args, **kwargs):
158
- t0 = time()
159
- res = func(*args, **kwargs)
160
- el = time() - t0
161
- return el, res
162
-
163
- return wrapper
164
-
165
-
166
- def wip(func):
167
- def wrapper(*args, **kwargs):
168
- func_name = func.__name__
169
- if func_name not in _warnings:
170
- _warnings[func_name] = 1
171
- print(
172
- "Warning: function %s is a work in progress, it is likely to change in the future"
173
- )
174
- return func(*args, **kwargs)
175
-
176
- return wrapper
177
-
178
-
179
- def warning(msg):
180
- def decorator(func):
181
- def wrapper(*args, **kwargs):
182
- func_name = func.__name__
183
- if func_name not in _warnings:
184
- _warnings[func_name] = 1
185
- print(msg)
186
- res = func(*args, **kwargs)
187
- return res
188
-
189
- return wrapper
190
-
191
- return decorator
192
-
193
-
194
- def log_work(func):
195
- def wrapper(*args, **kwargs):
196
- print("[%d] Executing %s ..." % (os.getpid(), func.__name__)) #  TODO in file ?
197
- res = func(*args, **kwargs)
198
- print("[%d] ... done" % os.getpid())
199
- return res
200
-
201
- return wrapper
File without changes
@@ -1,15 +0,0 @@
1
- class DeviceProxy:
2
- def ScanNumber(self):
3
- pass
4
-
5
- def State(self):
6
- pass
7
-
8
- def SequenceScanNumber(self):
9
- pass
10
-
11
- def SavingFile(self):
12
- pass
13
-
14
- def ScanTitle(self):
15
- pass