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.

@@ -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
- # The module_path is relative to this file's directory
32
- self.module_path = os.path.join(os.path.dirname(__file__), module_path)
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
- """Tries to load the CUDA module. If the file is missing, it attempts to compile it."""
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
- print(f"CUDA module {os.path.basename(self.module_path)} missing. Attempting compilation...")
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 after compilation (or if the file existed)
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
- raise RuntimeError("CUDA module not loaded. Check compilation.")
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
- dense_gpu = drv.mem_alloc(dense_host.nbytes)
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
- row_nnz_gpu_block = drv.mem_alloc(br * np.dtype(np.int32).itemsize)
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.sell_values_gpu = drv.mem_alloc(total_storage * np.dtype(np.float32).itemsize)
199
- self.sell_colinds_gpu = drv.mem_alloc(total_storage * np.dtype(np.uint32).itemsize)
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
- self.slice_ptr_gpu = drv.mem_alloc(self.slice_ptr.nbytes)
204
- self.slice_len_gpu = drv.mem_alloc(self.slice_len.nbytes)
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
- dense_gpu = drv.mem_alloc(dense_host.nbytes)
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
- row_nnz_host_gpu = drv.mem_alloc(br * np.dtype(np.int32).itemsize)
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), # rows_global_offset
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
- col_sum_gpu = drv.mem_alloc(ZX * np.dtype(np.float32).itemsize)
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
- acc_kernel = self.sparse_mod.get_function("accumulate_columns_atomic__SELL")
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
- self.norm_factor_inv_gpu = drv.mem_alloc(self.norm_factor_inv.nbytes)
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 # Returns only the density
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
- if hasattr(self, 'sell_values_gpu') and self.sell_values_gpu:
312
- total_bytes += self.sell_values_gpu.size
313
- if hasattr(self, 'sell_colinds_gpu') and self.sell_colinds_gpu:
314
- total_bytes += self.sell_colinds_gpu.size
315
- if hasattr(self, 'slice_ptr_gpu') and self.slice_ptr_gpu:
316
- total_bytes += self.slice_ptr_gpu.size
317
- if hasattr(self, 'slice_len_gpu') and self.slice_len_gpu:
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
@@ -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
- y=self.experiment.AOsignal_withTumor,
781
- numIterations=self.numIterations,
782
- isSavingEachIteration=self.isSavingEachIteration,
783
- withTumor=withTumor,
784
- alpha=self.alpha,
785
- max_saves=self.maxSaves,
786
- show_logs=show_logs
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
- y=self.experiment.AOsignal_withoutTumor,
791
- numIterations=self.numIterations,
792
- isSavingEachIteration=self.isSavingEachIteration,
793
- withTumor=withTumor,
794
- alpha=self.alpha,
795
- max_saves=self.maxSaves,
796
- show_logs=show_logs
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
- self.SMatrix,
157
- y=self.experiment.AOsignal_withTumor,
158
- alpha=self.alpha,
159
- theta=self.theta,
160
- numIterations=self.numIterations,
161
- isSavingEachIteration=self.isSavingEachIteration,
162
- L=self.L,
163
- withTumor=withTumor,
164
- device=None
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
- self.SMatrix,
169
- y=self.experiment.AOsignal_withoutTumor,
170
- alpha=self.alpha,
171
- theta=self.theta,
172
- numIterations=self.numIterations,
173
- isSavingEachIteration=self.isSavingEachIteration,
174
- L=self.L,
175
- withTumor=withTumor,
176
- device=None
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
- self.SMatrix,
182
- y=self.experiment.AOsignal_withTumor,
183
- alpha=self.alpha,
184
- theta=self.theta,
185
- numIterations=self.numIterations,
186
- isSavingEachIteration=self.isSavingEachIteration,
187
- L=self.L,
188
- withTumor=withTumor,
189
- device=None
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
- self.SMatrix,
194
- y=self.experiment.AOsignal_withoutTumor,
195
- alpha=self.alpha,
196
- theta=self.theta,
197
- numIterations=self.numIterations,
198
- isSavingEachIteration=self.isSavingEachIteration,
199
- L=self.L,
200
- withTumor=withTumor,
201
- device=None
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}")