AOT-biomaps 2.9.261__py3-none-any.whl → 2.9.294__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.
Potentially problematic release.
This version of AOT-biomaps might be problematic. Click here for more details.
- AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py +400 -10
- AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +60 -25
- AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +442 -11
- AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_CSR.py +48 -26
- AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_SELL.py +115 -109
- AOT_biomaps/AOT_Recon/AOT_biomaps_kernels.cubin +0 -0
- AOT_biomaps/AOT_Recon/AlgebraicRecon.py +27 -20
- AOT_biomaps/AOT_Recon/PrimalDualRecon.py +94 -41
- AOT_biomaps/AOT_Recon/ReconTools.py +164 -18
- AOT_biomaps/__init__.py +34 -1
- {aot_biomaps-2.9.261.dist-info → aot_biomaps-2.9.294.dist-info}/METADATA +1 -1
- {aot_biomaps-2.9.261.dist-info → aot_biomaps-2.9.294.dist-info}/RECORD +14 -13
- {aot_biomaps-2.9.261.dist-info → aot_biomaps-2.9.294.dist-info}/WHEEL +0 -0
- {aot_biomaps-2.9.261.dist-info → aot_biomaps-2.9.294.dist-info}/top_level.txt +0 -0
|
@@ -3,17 +3,6 @@ import numpy as np
|
|
|
3
3
|
from tqdm import trange
|
|
4
4
|
import os
|
|
5
5
|
import gc
|
|
6
|
-
import subprocess
|
|
7
|
-
import sys
|
|
8
|
-
|
|
9
|
-
# ==============================================================================
|
|
10
|
-
# ATTENTION: MANUAL CONFIGURATION REQUIRED
|
|
11
|
-
# ==============================================================================
|
|
12
|
-
# The 'sm_XX' value must correspond to the Compute Capability of your NVIDIA GPU.
|
|
13
|
-
# Examples: Kepler (sm_35), Maxwell (sm_50), Pascal (sm_61), Turing (sm_75), Ampere (sm_86).
|
|
14
|
-
# THIS LINE MUST BE MODIFIED BY THE USER to target their specific architecture.
|
|
15
|
-
GPU_COMPUTE_CAPABILITY = "sm_86"
|
|
16
|
-
# ==============================================================================
|
|
17
6
|
|
|
18
7
|
class SparseSMatrix_SELL:
|
|
19
8
|
def __init__(self, manip, block_rows=64, relative_threshold=0.3, device=0,
|
|
@@ -28,104 +17,71 @@ class SparseSMatrix_SELL:
|
|
|
28
17
|
self.X = manip.AcousticFields[0].field.shape[2]
|
|
29
18
|
self.block_rows = block_rows
|
|
30
19
|
self.relative_threshold = relative_threshold
|
|
31
|
-
|
|
32
|
-
|
|
20
|
+
|
|
21
|
+
# --- PATH RESOLUTION FIX ---
|
|
22
|
+
# The cubin file is located in the parent directory (AOT_Recon/)
|
|
23
|
+
# We use os.path.dirname(os.path.dirname(__file__)) to go up one directory level.
|
|
24
|
+
cubin_parent_dir = os.path.dirname(os.path.dirname(__file__))
|
|
25
|
+
self.module_path = os.path.join(cubin_parent_dir, module_path)
|
|
26
|
+
# --- END FIX ---
|
|
27
|
+
|
|
33
28
|
self.slice_height = slice_height
|
|
34
29
|
|
|
35
|
-
# SELL arrays (device)
|
|
30
|
+
# SELL arrays (device) & Size Tracking (CRITICAL FIX: Initialized attributes)
|
|
36
31
|
self.sell_values_gpu = None
|
|
37
32
|
self.sell_colinds_gpu = None
|
|
38
33
|
self.slice_ptr = None
|
|
39
34
|
self.slice_len = None
|
|
40
35
|
self.slice_ptr_gpu = None
|
|
41
36
|
self.slice_len_gpu = None
|
|
37
|
+
|
|
38
|
+
# Attributes to store allocated size in bytes (bypassing the problematic .size attribute)
|
|
39
|
+
self.sell_values_gpu_size = 0
|
|
40
|
+
self.sell_colinds_gpu_size = 0
|
|
41
|
+
self.slice_ptr_gpu_size = 0
|
|
42
|
+
self.slice_len_gpu_size = 0
|
|
43
|
+
|
|
42
44
|
self.total_storage = 0
|
|
43
45
|
|
|
44
46
|
self.norm_factor_inv = None
|
|
45
47
|
self.norm_factor_inv_gpu = None
|
|
48
|
+
self.norm_factor_inv_gpu_size = 0
|
|
46
49
|
|
|
47
50
|
self.sparse_mod = None
|
|
48
51
|
self.load_module()
|
|
49
|
-
|
|
50
|
-
def _compile_cubin(self, source_file="AOT_biomaps_kernels.cu"):
|
|
51
|
-
"""
|
|
52
|
-
Tries to compile the .cu file into .cubin using nvcc.
|
|
53
|
-
"""
|
|
54
|
-
print("="*60)
|
|
55
|
-
print("🛠️ CUDA COMPILATION REQUIRED")
|
|
56
|
-
print(f"Attempting to compile {source_file} to {os.path.basename(self.module_path)}...")
|
|
57
|
-
|
|
58
|
-
# The source file is assumed to be in the same directory as this Python file.
|
|
59
|
-
source_path = os.path.join(os.path.dirname(__file__), source_file)
|
|
60
|
-
cubin_path = self.module_path
|
|
61
|
-
|
|
62
|
-
if not os.path.exists(source_path):
|
|
63
|
-
print(f"❌ CRITICAL ERROR: CUDA source file {source_file} not found at {source_path}.")
|
|
64
|
-
raise FileNotFoundError(f"Could not find source file {source_file} for compilation. AOT_biomaps installation might be incomplete.")
|
|
65
|
-
|
|
66
|
-
# Construction of the nvcc command
|
|
67
|
-
command = [
|
|
68
|
-
'nvcc',
|
|
69
|
-
'-cubin',
|
|
70
|
-
f'-arch={GPU_COMPUTE_CAPABILITY}', # USES THE VARIABLE DEFINED ABOVE
|
|
71
|
-
source_path,
|
|
72
|
-
'-o',
|
|
73
|
-
cubin_path
|
|
74
|
-
]
|
|
75
|
-
|
|
76
|
-
print(f"Executing command: {' '.join(command)}")
|
|
77
|
-
|
|
78
|
-
try:
|
|
79
|
-
# Executes the command and waits for completion
|
|
80
|
-
result = subprocess.run(
|
|
81
|
-
command,
|
|
82
|
-
check=True,
|
|
83
|
-
capture_output=True,
|
|
84
|
-
text=True
|
|
85
|
-
)
|
|
86
|
-
print(f"🎉 Compilation successful! File created: {os.path.basename(cubin_path)}")
|
|
87
|
-
# print("Output nvcc:\n", result.stdout) # Uncomment to see detailed output
|
|
88
|
-
print("="*60)
|
|
89
|
-
return True
|
|
90
|
-
|
|
91
|
-
except subprocess.CalledProcessError as e:
|
|
92
|
-
print("❌ NVCC COMPILATION ERROR:")
|
|
93
|
-
print(f"Check GPU architecture: {GPU_COMPUTE_CAPABILITY}")
|
|
94
|
-
print(f"Standard error:\n{e.stderr}")
|
|
95
|
-
print("="*60)
|
|
96
|
-
return False
|
|
97
|
-
|
|
98
|
-
except FileNotFoundError:
|
|
99
|
-
print("❌ ERROR: The 'nvcc' command was not found.")
|
|
100
|
-
print("Ensure that the CUDA Toolkit is installed and 'nvcc' is in your PATH (or your Conda environment).")
|
|
101
|
-
print("="*60)
|
|
102
|
-
return False
|
|
103
52
|
|
|
104
53
|
def load_module(self):
|
|
105
|
-
"""
|
|
54
|
+
"""Loads the pre-compiled CUDA module (.cubin file)."""
|
|
106
55
|
|
|
56
|
+
# Check if the file exists at the calculated absolute path
|
|
107
57
|
if not os.path.exists(self.module_path):
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if not self._compile_cubin():
|
|
111
|
-
# If compilation fails, re-raise the original error.
|
|
112
|
-
raise FileNotFoundError(f"{os.path.basename(self.module_path)} not found and compilation failed. Check nvcc and GPU architecture ({GPU_COMPUTE_CAPABILITY}).")
|
|
58
|
+
# The path is now correctly calculated to the parent directory.
|
|
59
|
+
raise FileNotFoundError(f"CUDA module {os.path.basename(self.module_path)} not found at path: {self.module_path}")
|
|
113
60
|
|
|
114
|
-
# Try to load
|
|
61
|
+
# Try to load the module
|
|
115
62
|
try:
|
|
116
63
|
self.sparse_mod = drv.module_from_file(self.module_path)
|
|
117
64
|
print(f"Loaded CUDA module {os.path.basename(self.module_path)}")
|
|
118
65
|
except Exception as e:
|
|
119
66
|
print(f"❌ Error loading CUDA module {os.path.basename(self.module_path)}: {e}")
|
|
120
|
-
raise RuntimeError(f"File {os.path.basename(self.module_path)} was found, but PyCUDA could not load it.") from e
|
|
67
|
+
raise RuntimeError(f"File {os.path.basename(self.module_path)} was found, but PyCUDA could not load it. Check compatibility.") from e
|
|
121
68
|
|
|
122
69
|
def free(self):
|
|
123
70
|
try:
|
|
71
|
+
# Free device allocations
|
|
124
72
|
attrs = ["sell_values_gpu","sell_colinds_gpu","slice_ptr_gpu","slice_len_gpu","norm_factor_inv_gpu"]
|
|
125
73
|
for a in attrs:
|
|
126
74
|
if hasattr(self, a) and getattr(self, a) is not None:
|
|
127
75
|
getattr(self, a).free()
|
|
128
76
|
setattr(self, a, None)
|
|
77
|
+
|
|
78
|
+
# Reset stored sizes
|
|
79
|
+
self.sell_values_gpu_size = 0
|
|
80
|
+
self.sell_colinds_gpu_size = 0
|
|
81
|
+
self.slice_ptr_gpu_size = 0
|
|
82
|
+
self.slice_len_gpu_size = 0
|
|
83
|
+
self.norm_factor_inv_gpu_size = 0
|
|
84
|
+
|
|
129
85
|
if hasattr(self, 'ctx') and self.ctx:
|
|
130
86
|
try: self.ctx.pop()
|
|
131
87
|
except Exception: pass
|
|
@@ -136,11 +92,13 @@ class SparseSMatrix_SELL:
|
|
|
136
92
|
def allocate(self):
|
|
137
93
|
"""
|
|
138
94
|
Build SELL-C-σ directly from manip AcousticFields in streaming blocks.
|
|
95
|
+
NOTE: This is the logic of allocate_sell_c_sigma_direct from the working class.
|
|
139
96
|
"""
|
|
140
|
-
# Ensures the module is loaded before attempting to retrieve functions
|
|
141
97
|
if self.sparse_mod is None:
|
|
142
|
-
|
|
143
|
-
|
|
98
|
+
raise RuntimeError("CUDA module not loaded. Check compilation.")
|
|
99
|
+
|
|
100
|
+
# NOTE: Les noms de kernel (count_nnz_rows_kernel, fill_kernel__SELL) sont utilisés
|
|
101
|
+
# car ils sont présents dans la classe fonctionnelle.
|
|
144
102
|
count_kernel = self.sparse_mod.get_function("count_nnz_rows_kernel")
|
|
145
103
|
fill_kernel = self.sparse_mod.get_function("fill_kernel__SELL")
|
|
146
104
|
|
|
@@ -152,11 +110,15 @@ class SparseSMatrix_SELL:
|
|
|
152
110
|
br = int(self.block_rows)
|
|
153
111
|
bytes_per_elem = np.dtype(np.float32).itemsize
|
|
154
112
|
dense_host = np.empty((br, num_cols), dtype=np.float32)
|
|
155
|
-
|
|
113
|
+
|
|
114
|
+
# Allocation 1: Dense block GPU memory
|
|
115
|
+
dense_gpu_size = dense_host.nbytes
|
|
116
|
+
dense_gpu = drv.mem_alloc(dense_gpu_size)
|
|
156
117
|
|
|
157
118
|
# 1) count nnz per row (on host via small blocks with GPU kernel)
|
|
158
119
|
row_nnz = np.zeros(num_rows, dtype=np.int32)
|
|
159
|
-
|
|
120
|
+
row_nnz_gpu_block_size = br * np.dtype(np.int32).itemsize
|
|
121
|
+
row_nnz_gpu_block = drv.mem_alloc(row_nnz_gpu_block_size)
|
|
160
122
|
|
|
161
123
|
block = 256
|
|
162
124
|
for b in trange(0, num_rows, br, desc="Count NNZ per row"):
|
|
@@ -195,22 +157,35 @@ class SparseSMatrix_SELL:
|
|
|
195
157
|
print(f"SELL: num_rows={num_rows}, num_slices={num_slices}, total_storage(padded)={total_storage}")
|
|
196
158
|
|
|
197
159
|
# allocate device SELL arrays (values float32, colinds uint32)
|
|
198
|
-
self.
|
|
199
|
-
self.
|
|
160
|
+
self.sell_values_gpu_size = total_storage * np.dtype(np.float32).itemsize
|
|
161
|
+
self.sell_colinds_gpu_size = total_storage * np.dtype(np.uint32).itemsize
|
|
162
|
+
|
|
163
|
+
self.sell_values_gpu = drv.mem_alloc(self.sell_values_gpu_size)
|
|
164
|
+
self.sell_colinds_gpu = drv.mem_alloc(self.sell_colinds_gpu_size)
|
|
165
|
+
|
|
200
166
|
# allocate slice metadata on device
|
|
201
167
|
self.slice_ptr = slice_ptr
|
|
202
168
|
self.slice_len = slice_len
|
|
203
|
-
|
|
204
|
-
self.
|
|
169
|
+
|
|
170
|
+
self.slice_ptr_gpu_size = self.slice_ptr.nbytes
|
|
171
|
+
self.slice_len_gpu_size = self.slice_len.nbytes
|
|
172
|
+
|
|
173
|
+
self.slice_ptr_gpu = drv.mem_alloc(self.slice_ptr_gpu_size)
|
|
174
|
+
self.slice_len_gpu = drv.mem_alloc(self.slice_len_gpu_size)
|
|
175
|
+
|
|
205
176
|
drv.memcpy_htod(self.slice_ptr_gpu, self.slice_ptr)
|
|
206
177
|
drv.memcpy_htod(self.slice_len_gpu, self.slice_len)
|
|
207
178
|
|
|
208
179
|
# 3) fill SELL arrays by streaming blocks again (use GPU fill kernel)
|
|
209
|
-
# reuse dense_host and dense_gpu
|
|
180
|
+
# reuse dense_host and allocate new dense_gpu
|
|
210
181
|
dense_host = np.empty((br, num_cols), dtype=np.float32)
|
|
211
|
-
|
|
182
|
+
|
|
183
|
+
dense_gpu_2_size = dense_host.nbytes
|
|
184
|
+
dense_gpu = drv.mem_alloc(dense_gpu_2_size)
|
|
185
|
+
|
|
212
186
|
# we also need row_nnz on device per-block; supply global row_nnz on host but the kernel recomputes threshold
|
|
213
|
-
|
|
187
|
+
row_nnz_host_gpu_size = br * np.dtype(np.int32).itemsize
|
|
188
|
+
row_nnz_host_gpu = drv.mem_alloc(row_nnz_host_gpu_size)
|
|
214
189
|
|
|
215
190
|
for b in trange(0, num_rows, br, desc="Fill SELL"):
|
|
216
191
|
R = min(br, num_rows - b)
|
|
@@ -231,18 +206,49 @@ class SparseSMatrix_SELL:
|
|
|
231
206
|
self.sell_values_gpu,
|
|
232
207
|
np.int32(R),
|
|
233
208
|
np.int32(num_cols),
|
|
234
|
-
np.int32(b),
|
|
209
|
+
np.int32(b), # rows_global_offset
|
|
235
210
|
np.int32(C),
|
|
236
211
|
np.float32(self.relative_threshold),
|
|
237
212
|
block=(block,1,1), grid=grid)
|
|
238
213
|
dense_gpu.free()
|
|
239
214
|
row_nnz_host_gpu.free()
|
|
240
215
|
|
|
241
|
-
# At this point sell_values_gpu and sell_colinds_gpu are filled.
|
|
242
|
-
|
|
243
216
|
# 4) compute norm_factor_inv via GPU accumulate (col sums)
|
|
244
217
|
self.compute_norm_factor()
|
|
218
|
+
|
|
219
|
+
def apply_apodization_gpu(self, window_vector_gpu):
|
|
220
|
+
"""
|
|
221
|
+
Applique le fenêtrage directement sur self.sell_values_gpu
|
|
222
|
+
en utilisant les indices de colonnes (pixels) pour référencer
|
|
223
|
+
la fenêtre. Opération : A_values[i] *= W_vec[A_colinds[i]].
|
|
224
|
+
"""
|
|
225
|
+
if self.sparse_mod is None:
|
|
226
|
+
raise RuntimeError("Le module CUDA n'a pas été chargé.")
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
apodize_kernel = self.sparse_mod.get_function("apply_apodisation_kernel__SELL")
|
|
230
|
+
except drv.LogicError as e:
|
|
231
|
+
raise RuntimeError(
|
|
232
|
+
f"Le kernel CUDA 'multiply_sell_by_window_kernel' est manquant dans le .cubin. "
|
|
233
|
+
f"Veuillez le compiler et l'ajouter. Erreur : {e}"
|
|
234
|
+
)
|
|
245
235
|
|
|
236
|
+
# Le total_storage inclut les éléments non-nuls et le padding SELL.
|
|
237
|
+
threads = 256
|
|
238
|
+
blocks = (self.total_storage + threads - 1) // threads
|
|
239
|
+
|
|
240
|
+
# Lancement du kernel. Il travaille sur total_storage éléments.
|
|
241
|
+
apodize_kernel(
|
|
242
|
+
self.sell_values_gpu,
|
|
243
|
+
self.sell_colinds_gpu,
|
|
244
|
+
window_vector_gpu,
|
|
245
|
+
np.int64(self.total_storage),
|
|
246
|
+
block=(threads, 1, 1),
|
|
247
|
+
grid=(blocks, 1, 1)
|
|
248
|
+
)
|
|
249
|
+
drv.Context.synchronize()
|
|
250
|
+
print("✅ Multiplication par le fenêtrage effectuée in-place sur GPU (SELL-C-σ).")
|
|
251
|
+
# --- Ajout de la fonction de normalisation (qui fonctionne) ---
|
|
246
252
|
def compute_norm_factor(self):
|
|
247
253
|
"""
|
|
248
254
|
Accumulate column sums on GPU using accumulate_columns_atomic, then compute inverse.
|
|
@@ -252,10 +258,13 @@ class SparseSMatrix_SELL:
|
|
|
252
258
|
ZX = int(self.Z * self.X)
|
|
253
259
|
|
|
254
260
|
# allocate col sum on device
|
|
255
|
-
|
|
261
|
+
col_sum_gpu_size = ZX * np.dtype(np.float32).itemsize
|
|
262
|
+
col_sum_gpu = drv.mem_alloc(col_sum_gpu_size)
|
|
256
263
|
drv.memset_d32(col_sum_gpu, 0, ZX)
|
|
257
264
|
|
|
258
|
-
|
|
265
|
+
# FIX: Kernel name is "accumulate_columns_atomic"
|
|
266
|
+
acc_kernel = self.sparse_mod.get_function("accumulate_columns_atomic")
|
|
267
|
+
|
|
259
268
|
threads = 256
|
|
260
269
|
blocks = (self.total_storage + threads - 1) // threads
|
|
261
270
|
acc_kernel(self.sell_values_gpu, self.sell_colinds_gpu, np.int64(self.total_storage), col_sum_gpu,
|
|
@@ -271,7 +280,9 @@ class SparseSMatrix_SELL:
|
|
|
271
280
|
self.norm_factor_inv = (1.0 / norm).astype(np.float32)
|
|
272
281
|
if self.norm_factor_inv_gpu is not None:
|
|
273
282
|
self.norm_factor_inv_gpu.free()
|
|
274
|
-
|
|
283
|
+
|
|
284
|
+
self.norm_factor_inv_gpu_size = self.norm_factor_inv.nbytes
|
|
285
|
+
self.norm_factor_inv_gpu = drv.mem_alloc(self.norm_factor_inv_gpu_size)
|
|
275
286
|
drv.memcpy_htod(self.norm_factor_inv_gpu, self.norm_factor_inv)
|
|
276
287
|
|
|
277
288
|
def compute_density(self):
|
|
@@ -288,7 +299,7 @@ class SparseSMatrix_SELL:
|
|
|
288
299
|
# Conservative estimate of non-zeros (excluding padding)
|
|
289
300
|
nnz_ell_estimated = int(0.9 * self.total_storage)
|
|
290
301
|
|
|
291
|
-
return nnz_ell_estimated / total_elements
|
|
302
|
+
return nnz_ell_estimated / total_elements # Returns only the density
|
|
292
303
|
|
|
293
304
|
def getMatrixSize(self):
|
|
294
305
|
"""
|
|
@@ -299,7 +310,7 @@ class SparseSMatrix_SELL:
|
|
|
299
310
|
|
|
300
311
|
total_bytes = 0
|
|
301
312
|
|
|
302
|
-
# Host-side arrays
|
|
313
|
+
# Host-side arrays (using .nbytes which works for NumPy arrays)
|
|
303
314
|
if hasattr(self, 'slice_ptr') and self.slice_ptr is not None:
|
|
304
315
|
total_bytes += self.slice_ptr.nbytes
|
|
305
316
|
if hasattr(self, 'slice_len') and self.slice_len is not None:
|
|
@@ -307,16 +318,11 @@ class SparseSMatrix_SELL:
|
|
|
307
318
|
if hasattr(self, 'norm_factor_inv') and self.norm_factor_inv is not None:
|
|
308
319
|
total_bytes += self.norm_factor_inv.nbytes
|
|
309
320
|
|
|
310
|
-
# GPU-side arrays
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
total_bytes += self.slice_len_gpu.size
|
|
319
|
-
if hasattr(self, 'norm_factor_inv_gpu') and self.norm_factor_inv_gpu:
|
|
320
|
-
total_bytes += self.norm_factor_inv_gpu.size
|
|
321
|
-
|
|
322
|
-
return total_bytes / (1024 ** 3) # Returns only the size in GB
|
|
321
|
+
# GPU-side arrays (using the stored size attributes instead of the problematic .size)
|
|
322
|
+
total_bytes += self.sell_values_gpu_size
|
|
323
|
+
total_bytes += self.sell_colinds_gpu_size
|
|
324
|
+
total_bytes += self.slice_ptr_gpu_size
|
|
325
|
+
total_bytes += self.slice_len_gpu_size
|
|
326
|
+
total_bytes += self.norm_factor_inv_gpu_size
|
|
327
|
+
|
|
328
|
+
return total_bytes / (1024 ** 3) # Returns only the size in GB
|
|
Binary file
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import concurrent
|
|
2
|
+
|
|
3
|
+
from AOT_biomaps.AOT_Recon.ReconTools import get_apodization_vector_gpu
|
|
2
4
|
from ._mainRecon import Recon
|
|
3
5
|
from .ReconEnums import ReconType, OptimizerType, ProcessType, SMatrixType
|
|
4
6
|
from .AOT_Optimizers import MLEM, LS
|
|
@@ -43,8 +45,6 @@ class AlgebraicRecon(Recon):
|
|
|
43
45
|
|
|
44
46
|
self.sparseThreshold = sparseThreshold
|
|
45
47
|
|
|
46
|
-
self.Z_dim = None # Used for sparse matrix reconstruction
|
|
47
|
-
|
|
48
48
|
if self.numIterations <= 0:
|
|
49
49
|
raise ValueError("Number of iterations must be greater than 0.")
|
|
50
50
|
if self.numSubsets <= 0:
|
|
@@ -729,6 +729,8 @@ class AlgebraicRecon(Recon):
|
|
|
729
729
|
"""
|
|
730
730
|
sparse_matrix = SparseSMatrix_SELL(self.experiment,relative_threshold=self.sparseThreshold)
|
|
731
731
|
sparse_matrix.allocate()
|
|
732
|
+
# fenetre_gpu = get_apodization_vector_gpu(sparse_matrix)
|
|
733
|
+
# sparse_matrix.apply_apodization_gpu(fenetre_gpu)
|
|
732
734
|
if isShowLogs:
|
|
733
735
|
print(f" Sparse matrix size: {sparse_matrix.getMatrixSize()} GB")
|
|
734
736
|
print(f"Sparse matrix density: {sparse_matrix.compute_density()}")
|
|
@@ -756,7 +758,6 @@ class AlgebraicRecon(Recon):
|
|
|
756
758
|
max_saves=self.maxSaves,
|
|
757
759
|
show_logs=show_logs,
|
|
758
760
|
smatrixType=self.smatrixType,
|
|
759
|
-
Z=self.Z_dim
|
|
760
761
|
)
|
|
761
762
|
else:
|
|
762
763
|
self.reconLaser, self.indices = MLEM(SMatrix=self.SMatrix,
|
|
@@ -770,30 +771,36 @@ class AlgebraicRecon(Recon):
|
|
|
770
771
|
max_saves=self.maxSaves,
|
|
771
772
|
show_logs=show_logs,
|
|
772
773
|
smatrixType=self.smatrixType,
|
|
773
|
-
Z=self.Z_dim
|
|
774
774
|
)
|
|
775
775
|
elif self.optimizer.value == OptimizerType.LS.value:
|
|
776
776
|
if self.alpha is None:
|
|
777
777
|
raise ValueError("Alpha (regularization parameter) must be set for LS reconstruction.")
|
|
778
778
|
if withTumor:
|
|
779
|
-
self.reconPhantom, self.indices = LS(SMatrix=self.SMatrix,
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
779
|
+
self.reconPhantom, self.indices = LS(SMatrix=self.SMatrix,
|
|
780
|
+
y=self.experiment.AOsignal_withTumor,
|
|
781
|
+
numIterations=self.numIterations,
|
|
782
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
783
|
+
withTumor=withTumor,
|
|
784
|
+
device=self.device,
|
|
785
|
+
use_numba=self.isMultiCPU,
|
|
786
|
+
denominator_threshold=self.denominatorThreshold,
|
|
787
|
+
max_saves=self.maxSaves,
|
|
788
|
+
show_logs=show_logs,
|
|
789
|
+
smatrixType=self.smatrixType
|
|
787
790
|
)
|
|
788
791
|
else:
|
|
789
|
-
self.reconLaser, self.indices = LS(SMatrix=self.SMatrix,
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
792
|
+
self.reconLaser, self.indices = LS(SMatrix=self.SMatrix,
|
|
793
|
+
y=self.experiment.AOsignal_withoutTumor,
|
|
794
|
+
numIterations=self.numIterations,
|
|
795
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
796
|
+
withTumor=withTumor,
|
|
797
|
+
alpha=self.alpha,
|
|
798
|
+
device=self.device,
|
|
799
|
+
use_numba=self.isMultiCPU,
|
|
800
|
+
denominator_threshold=self.denominatorThreshold,
|
|
801
|
+
max_saves=self.maxSaves,
|
|
802
|
+
show_logs=show_logs,
|
|
803
|
+
smatrixType=self.smatrixType
|
|
797
804
|
)
|
|
798
805
|
else:
|
|
799
806
|
raise ValueError(f"Only MLEM and LS are supported for simple algebraic reconstruction. {self.optimizer.value} need Bayesian reconstruction")
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from AOT_biomaps.AOT_Recon.AlgebraicRecon import AlgebraicRecon
|
|
2
|
-
from AOT_biomaps.AOT_Recon.ReconEnums import ReconType, ProcessType
|
|
2
|
+
from AOT_biomaps.AOT_Recon.ReconEnums import ReconType, ProcessType, SMatrixType
|
|
3
3
|
from AOT_biomaps.AOT_Recon.AOT_Optimizers import CP_KL, CP_TV
|
|
4
4
|
from AOT_biomaps.AOT_Recon.ReconEnums import OptimizerType
|
|
5
5
|
|
|
@@ -13,11 +13,20 @@ class PrimalDualRecon(AlgebraicRecon):
|
|
|
13
13
|
This class implements the convex reconstruction process.
|
|
14
14
|
It currently does not perform any operations but serves as a template for future implementations.
|
|
15
15
|
"""
|
|
16
|
-
def __init__(self, theta=1.0, L=None, **kwargs):
|
|
16
|
+
def __init__(self, alpha, beta, theta=1.0, L=None, k_security=0.8, use_power_method=True, auto_alpha_gamma=0.05, apply_positivity_clamp=True, tikhonov_as_gradient=False, use_laplacian=True, laplacian_beta_scale=1.0, **kwargs):
|
|
17
17
|
super().__init__(**kwargs)
|
|
18
18
|
self.reconType = ReconType.Convex
|
|
19
|
+
self.alpha = alpha # TV regularization parameter (if None, alpha is auto-scaled)
|
|
20
|
+
self.beta=beta # Tikhonov regularization parameter
|
|
19
21
|
self.theta = theta # relaxation parameter (between 1 and 2)
|
|
20
22
|
self.L = L # norme spectrale de l'opérateur linéaire défini par les matrices P et P^T
|
|
23
|
+
self.k_security=k_security
|
|
24
|
+
self.use_power_method=use_power_method
|
|
25
|
+
self.auto_alpha_gamma=auto_alpha_gamma # gamma for auto alpha: alpha = gamma * data_term / tv_term
|
|
26
|
+
self.apply_positivity_clamp=apply_positivity_clamp
|
|
27
|
+
self.tikhonov_as_gradient=tikhonov_as_gradient # if True, apply -tau*2*beta*x instead of prox multiplicative
|
|
28
|
+
self.use_laplacian=use_laplacian # enable Laplacian (Hessian scalar) penalty
|
|
29
|
+
self.laplacian_beta_scale=laplacian_beta_scale # multiply beta for laplacian term if you want separate scaling
|
|
21
30
|
|
|
22
31
|
def run(self, processType=ProcessType.PYTHON, withTumor=True):
|
|
23
32
|
"""
|
|
@@ -149,56 +158,100 @@ class PrimalDualRecon(AlgebraicRecon):
|
|
|
149
158
|
if show_logs:
|
|
150
159
|
print(f"Loaded reconstruction results and indices from {results_dir}")
|
|
151
160
|
|
|
152
|
-
def _convexReconPython(self, withTumor):
|
|
161
|
+
def _convexReconPython(self, withTumor,show_logs=True):
|
|
153
162
|
if self.optimizer == OptimizerType.CP_TV:
|
|
154
163
|
if withTumor:
|
|
155
164
|
self.reconPhantom, self.indices = CP_TV(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
165
|
+
SMatrix = self.SMatrix,
|
|
166
|
+
y = self.experiment.AOsignal_withTumor,
|
|
167
|
+
alpha=self.alpha,
|
|
168
|
+
beta=self.beta,
|
|
169
|
+
theta=self.theta,
|
|
170
|
+
numIterations=self.numIterations,
|
|
171
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
172
|
+
L=self.L,
|
|
173
|
+
withTumor=withTumor,
|
|
174
|
+
device=self.device,
|
|
175
|
+
max_saves=self.maxSaves,
|
|
176
|
+
show_logs=show_logs,
|
|
177
|
+
smatrixType= self.smatrixType,
|
|
178
|
+
k_security=self.k_security,
|
|
179
|
+
use_power_method=self.use_power_method,
|
|
180
|
+
auto_alpha_gamma=self.auto_alpha_gamma,
|
|
181
|
+
apply_positivity_clamp=self.apply_positivity_clamp,
|
|
182
|
+
tikhonov_as_gradient=self.tikhonov_as_gradient,
|
|
183
|
+
use_laplacian=self.use_laplacian,
|
|
184
|
+
laplacian_beta_scale=self.laplacian_beta_scale
|
|
185
|
+
)
|
|
166
186
|
else:
|
|
167
187
|
self.reconLaser, self.indices = CP_TV(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
188
|
+
SMatrix = self.SMatrix,
|
|
189
|
+
y = self.experiment.AOsignal_withoutTumor,
|
|
190
|
+
alpha=self.alpha,
|
|
191
|
+
beta=self.beta,
|
|
192
|
+
theta=self.theta,
|
|
193
|
+
numIterations=self.numIterations,
|
|
194
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
195
|
+
L=self.L,
|
|
196
|
+
withTumor=withTumor,
|
|
197
|
+
device=self.device,
|
|
198
|
+
max_saves=self.maxSaves,
|
|
199
|
+
show_logs=show_logs,
|
|
200
|
+
smatrixType= self.smatrixType,
|
|
201
|
+
k_security=self.k_security,
|
|
202
|
+
use_power_method=self.use_power_method,
|
|
203
|
+
auto_alpha_gamma=self.auto_alpha_gamma,
|
|
204
|
+
apply_positivity_clamp=self.apply_positivity_clamp,
|
|
205
|
+
tikhonov_as_gradient=self.tikhonov_as_gradient,
|
|
206
|
+
use_laplacian=self.use_laplacian,
|
|
207
|
+
laplacian_beta_scale=self.laplacian_beta_scale
|
|
208
|
+
)
|
|
178
209
|
elif self.optimizer == OptimizerType.CP_KL:
|
|
179
210
|
if withTumor:
|
|
180
211
|
self.reconPhantom, self.indices = CP_KL(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
212
|
+
SMatrix = self.SMatrix,
|
|
213
|
+
y = self.experiment.AOsignal_withTumor,
|
|
214
|
+
alpha=self.alpha,
|
|
215
|
+
beta=self.beta,
|
|
216
|
+
theta=self.theta,
|
|
217
|
+
numIterations=self.numIterations,
|
|
218
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
219
|
+
L=self.L,
|
|
220
|
+
withTumor=withTumor,
|
|
221
|
+
device=self.device,
|
|
222
|
+
max_saves=self.maxSaves,
|
|
223
|
+
show_logs=show_logs,
|
|
224
|
+
smatrixType= self.smatrixType,
|
|
225
|
+
k_security=self.k_security,
|
|
226
|
+
use_power_method=self.use_power_method,
|
|
227
|
+
auto_alpha_gamma=self.auto_alpha_gamma,
|
|
228
|
+
apply_positivity_clamp=self.apply_positivity_clamp,
|
|
229
|
+
tikhonov_as_gradient=self.tikhonov_as_gradient,
|
|
230
|
+
use_laplacian=self.use_laplacian,
|
|
231
|
+
laplacian_beta_scale=self.laplacian_beta_scale
|
|
190
232
|
)
|
|
191
233
|
else:
|
|
192
234
|
self.reconLaser, self.indices = CP_KL(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
235
|
+
SMatrix = self.SMatrix,
|
|
236
|
+
y = self.experiment.AOsignal_withoutTumor,
|
|
237
|
+
alpha=self.alpha,
|
|
238
|
+
beta=self.beta,
|
|
239
|
+
theta=self.theta,
|
|
240
|
+
numIterations=self.numIterations,
|
|
241
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
242
|
+
L=self.L,
|
|
243
|
+
withTumor=withTumor,
|
|
244
|
+
device=self.device,
|
|
245
|
+
max_saves=self.maxSaves,
|
|
246
|
+
show_logs=show_logs,
|
|
247
|
+
smatrixType= self.smatrixType,
|
|
248
|
+
k_security=self.k_security,
|
|
249
|
+
use_power_method=self.use_power_method,
|
|
250
|
+
auto_alpha_gamma=self.auto_alpha_gamma,
|
|
251
|
+
apply_positivity_clamp=self.apply_positivity_clamp,
|
|
252
|
+
tikhonov_as_gradient=self.tikhonov_as_gradient,
|
|
253
|
+
use_laplacian=self.use_laplacian,
|
|
254
|
+
laplacian_beta_scale=self.laplacian_beta_scale
|
|
202
255
|
)
|
|
203
256
|
else:
|
|
204
257
|
raise ValueError(f"Optimizer value must be CP_TV or CP_KL, got {self.optimizer}")
|