AOT-biomaps 2.9.202__tar.gz → 2.9.281__tar.gz
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-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Acoustic/StructuredWave.py +1 -1
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Acoustic/_mainAcoustic.py +7 -5
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Experiment/Tomography.py +74 -5
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Experiment/_mainExperiment.py +94 -77
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py +48 -13
- aot_biomaps-2.9.281/AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py +499 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py +118 -38
- aot_biomaps-2.9.281/AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +464 -0
- aot_biomaps-2.9.281/AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +555 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/RelativeDifferences.py +10 -14
- aot_biomaps-2.9.281/AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_CSR.py +281 -0
- aot_biomaps-2.9.281/AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_SELL.py +328 -0
- aot_biomaps-2.9.281/AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/__init__.py +2 -0
- aot_biomaps-2.9.281/AOT_biomaps/AOT_Recon/AOT_biomaps_kernels.cubin +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AlgebraicRecon.py +218 -108
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AnalyticRecon.py +26 -41
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/BayesianRecon.py +33 -96
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/PrimalDualRecon.py +14 -18
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/ReconEnums.py +27 -2
- aot_biomaps-2.9.281/AOT_biomaps/AOT_Recon/ReconTools.py +489 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/__init__.py +1 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/_mainRecon.py +58 -61
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/__init__.py +4 -98
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps.egg-info/PKG-INFO +2 -1
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps.egg-info/SOURCES.txt +5 -1
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps.egg-info/requires.txt +1 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/PKG-INFO +2 -1
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/setup.py +90 -1
- aot_biomaps-2.9.202/AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py +0 -103
- aot_biomaps-2.9.202/AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +0 -262
- aot_biomaps-2.9.202/AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +0 -221
- aot_biomaps-2.9.202/AOT_biomaps/AOT_Recon/ReconTools.py +0 -272
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Acoustic/AcousticEnums.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Acoustic/AcousticTools.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Acoustic/FocusedWave.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Acoustic/IrregularWave.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Acoustic/PlaneWave.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Acoustic/__init__.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Experiment/Focus.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Experiment/__init__.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Optic/Absorber.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Optic/Laser.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Optic/OpticEnums.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Optic/__init__.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Optic/_mainOptic.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AOT_Optimizers/__init__.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Huber.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Quadratic.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/__init__.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/DeepLearningRecon.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/Config.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/Settings.py +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps.egg-info/dependency_links.txt +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps.egg-info/top_level.txt +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/README.md +0 -0
- {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/setup.cfg +0 -0
|
@@ -293,7 +293,7 @@ class StructuredWave(AcousticField):
|
|
|
293
293
|
f"!number of bytes per pixel := 4\n"
|
|
294
294
|
f"scaling factor (mm/pixel) [1] := {self.params['dx'] * 1000}\n"
|
|
295
295
|
f"scaling factor (mm/pixel) [2] := {self.params['dz'] * 1000}\n"
|
|
296
|
-
f"scaling factor (s/pixel) [3] := {1 / self.params['
|
|
296
|
+
f"scaling factor (s/pixel) [3] := {1 / self.params['f_saving']}\n"
|
|
297
297
|
f"first pixel offset (mm) [1] := {self.params['Xrange'][0] * 1e3}\n"
|
|
298
298
|
f"first pixel offset (mm) [2] := {self.params['Zrange'][0] * 1e3}\n"
|
|
299
299
|
f"first pixel offset (s) [3] := 0\n"
|
|
@@ -268,7 +268,7 @@ class AcousticField(ABC):
|
|
|
268
268
|
print(f"Error in save_field method: {e}")
|
|
269
269
|
raise
|
|
270
270
|
|
|
271
|
-
def load_field(self, folderPath, formatSave=FormatSave.HDR_IMG):
|
|
271
|
+
def load_field(self, folderPath, formatSave=FormatSave.HDR_IMG, nameBlock=None):
|
|
272
272
|
"""
|
|
273
273
|
Load the acoustic field from a file in the specified format.
|
|
274
274
|
|
|
@@ -289,7 +289,7 @@ class AcousticField(ABC):
|
|
|
289
289
|
raise NotImplementedError("3D KWAVE field loading is not implemented yet.")
|
|
290
290
|
elif formatSave.value == FormatSave.H5.value:
|
|
291
291
|
if self.params["dim"] == Dim.D2.value:
|
|
292
|
-
self._load_field_h5(folderPath)
|
|
292
|
+
self._load_field_h5(folderPath,nameBlock)
|
|
293
293
|
elif self.params["dim"] == Dim.D3.value:
|
|
294
294
|
raise NotImplementedError("H5 KWAVE field loading is not implemented yet.")
|
|
295
295
|
elif formatSave.value == FormatSave.NPY.value:
|
|
@@ -655,7 +655,7 @@ class AcousticField(ABC):
|
|
|
655
655
|
"""
|
|
656
656
|
pass
|
|
657
657
|
|
|
658
|
-
def _load_field_h5(self, filePath):
|
|
658
|
+
def _load_field_h5(self, filePath,nameBlock):
|
|
659
659
|
"""
|
|
660
660
|
Load the 2D acoustic field from an H5 file.
|
|
661
661
|
|
|
@@ -666,8 +666,10 @@ class AcousticField(ABC):
|
|
|
666
666
|
- field (numpy.ndarray): The loaded acoustic field.
|
|
667
667
|
"""
|
|
668
668
|
try:
|
|
669
|
-
|
|
670
|
-
|
|
669
|
+
if nameBlock is None:
|
|
670
|
+
nameBlock = 'data'
|
|
671
|
+
with h5py.File(os.path.join(filePath, self.getName_field()+".h5"), 'r') as f:
|
|
672
|
+
self.field = f[nameBlock][:]
|
|
671
673
|
except Exception as e:
|
|
672
674
|
print(f"Error in _load_field_h5 method: {e}")
|
|
673
675
|
raise
|
|
@@ -7,6 +7,8 @@ import psutil
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import matplotlib.pyplot as plt
|
|
9
9
|
from tqdm import trange
|
|
10
|
+
import h5py
|
|
11
|
+
from scipy.io import loadmat
|
|
10
12
|
|
|
11
13
|
class Tomography(Experiment):
|
|
12
14
|
def __init__(self, **kwargs):
|
|
@@ -47,7 +49,7 @@ class Tomography(Experiment):
|
|
|
47
49
|
return False, f"OpticImage phantom shape {self.OpticImage.phantom.shape} does not match AcousticFields shape {self.AcousticFields[0].field.shape[1:]}."
|
|
48
50
|
return True, "Experiment is correctly initialized."
|
|
49
51
|
|
|
50
|
-
def generateAcousticFields(self, fieldDataPath=None, show_log=True):
|
|
52
|
+
def generateAcousticFields(self, fieldDataPath=None, show_log=True, nameBlock=None):
|
|
51
53
|
"""
|
|
52
54
|
Generate the acoustic fields for simulation.
|
|
53
55
|
Args:
|
|
@@ -57,8 +59,7 @@ class Tomography(Experiment):
|
|
|
57
59
|
systemMatrix: A numpy array of the generated fields.
|
|
58
60
|
"""
|
|
59
61
|
if self.TypeAcoustic.value == WaveType.StructuredWave.value:
|
|
60
|
-
self.
|
|
61
|
-
self.AcousticFields = self._generateAcousticFields_STRUCT_CPU(fieldDataPath, show_log)
|
|
62
|
+
self.AcousticFields = self._generateAcousticFields_STRUCT_CPU(fieldDataPath, show_log, nameBlock)
|
|
62
63
|
else:
|
|
63
64
|
raise ValueError("Unsupported wave type.")
|
|
64
65
|
|
|
@@ -390,7 +391,7 @@ class Tomography(Experiment):
|
|
|
390
391
|
return True
|
|
391
392
|
|
|
392
393
|
# PRIVATE METHODS
|
|
393
|
-
def _generateAcousticFields_STRUCT_CPU(self, fieldDataPath=None, show_log=False):
|
|
394
|
+
def _generateAcousticFields_STRUCT_CPU(self, fieldDataPath=None, show_log=False, nameBlock=None):
|
|
394
395
|
if self.patterns is None:
|
|
395
396
|
raise ValueError("patterns is not initialized. Please load or generate the active list first.")
|
|
396
397
|
listAcousticFields = []
|
|
@@ -416,7 +417,7 @@ class Tomography(Experiment):
|
|
|
416
417
|
if pathField is not None and os.path.exists(pathField):
|
|
417
418
|
progress_bar.set_postfix_str(f"Loading field - {AcousticField.getName_field()} -- Memory used: {memory.percent}%")
|
|
418
419
|
try:
|
|
419
|
-
AcousticField.load_field(fieldDataPath, self.FormatSave)
|
|
420
|
+
AcousticField.load_field(fieldDataPath, self.FormatSave,nameBlock)
|
|
420
421
|
except:
|
|
421
422
|
progress_bar.set_postfix_str(f"Error loading field -> Generating field - {AcousticField.getName_field()} -- Memory used: {memory.percent}% ---- processing on {config.get_process().upper()} ----")
|
|
422
423
|
AcousticField.generate_field(show_log=show_log)
|
|
@@ -434,3 +435,71 @@ class Tomography(Experiment):
|
|
|
434
435
|
listAcousticFields.append(AcousticField)
|
|
435
436
|
progress_bar.set_postfix_str("")
|
|
436
437
|
return listAcousticFields
|
|
438
|
+
|
|
439
|
+
def load_experimentalAO(self, pathAO, withTumor = True, h5name='AOsignal'):
|
|
440
|
+
"""
|
|
441
|
+
Load experimental AO signals from specified file paths.
|
|
442
|
+
Args:
|
|
443
|
+
path_withTumor: Path to the AO signal with tumor.
|
|
444
|
+
path_withoutTumor: Path to the AO signal without tumor.
|
|
445
|
+
"""
|
|
446
|
+
if not os.path.exists(pathAO):
|
|
447
|
+
raise FileNotFoundError(f"File {pathAO} not found.")
|
|
448
|
+
|
|
449
|
+
if pathAO.endswith('.npy'):
|
|
450
|
+
ao_signal = np.load(pathAO)
|
|
451
|
+
elif pathAO.endswith('.h5'):
|
|
452
|
+
with h5py.File(pathAO, 'r') as f:
|
|
453
|
+
if h5name not in f:
|
|
454
|
+
raise KeyError(f"Dataset '{h5name}' not found in the HDF5 file.")
|
|
455
|
+
ao_signal = f[h5name][:]
|
|
456
|
+
elif pathAO.endswith('.mat'):
|
|
457
|
+
mat_data = loadmat(pathAO)
|
|
458
|
+
if h5name not in mat_data:
|
|
459
|
+
raise KeyError(f"Dataset '{h5name}' not found in the .mat file.")
|
|
460
|
+
ao_signal = mat_data[h5name]
|
|
461
|
+
elif pathAO.endswith('.hdr'):
|
|
462
|
+
ao_signal = self._loadAOSignal(pathAO)
|
|
463
|
+
else:
|
|
464
|
+
raise ValueError("Unsupported file format. Supported formats are: .npy, .h5, .mat, .hdr")
|
|
465
|
+
|
|
466
|
+
if withTumor:
|
|
467
|
+
self.AOsignal_withTumor = ao_signal
|
|
468
|
+
else:
|
|
469
|
+
self.AOsignal_withoutTumor = ao_signal
|
|
470
|
+
|
|
471
|
+
def check_experimentalAO(self, activeListPath, withTumor=True):
|
|
472
|
+
"""
|
|
473
|
+
Check if the experimental AO signals are correctly initialized.
|
|
474
|
+
"""
|
|
475
|
+
if withTumor:
|
|
476
|
+
if self.AOsignal_withTumor is None:
|
|
477
|
+
raise ValueError("Experimental AOsignal with tumor is not initialized. Please load the experimental AO signal with tumor first.")
|
|
478
|
+
else:
|
|
479
|
+
if self.AOsignal_withoutTumor is None:
|
|
480
|
+
raise ValueError("Experimental AOsignal without tumor is not initialized. Please load the experimental AO signal without tumor first.")
|
|
481
|
+
if self.AcousticFields is not None:
|
|
482
|
+
# get min time shape between all AO signals
|
|
483
|
+
print()
|
|
484
|
+
|
|
485
|
+
if self.AcousticFields[0].field.shape[0] > self.AOsignal_withTumor.shape[0]:
|
|
486
|
+
self.cutAcousticFields(max_t=self.AOsignal_withTumor.shape[0]/float(self.params.acoustic['f_saving']))
|
|
487
|
+
else:
|
|
488
|
+
for i in range(len(self.AcousticFields)):
|
|
489
|
+
min_time_shape = min(self.AcousticFields[i].field.shape[0])
|
|
490
|
+
if withTumor:
|
|
491
|
+
self.AOsignal_withTumor = self.AOsignal_withTumor[:min_time_shape, :]
|
|
492
|
+
else:
|
|
493
|
+
self.AOsignal_withoutTumor = self.AOsignal_withoutTumor[:min_time_shape, :]
|
|
494
|
+
|
|
495
|
+
for field in self.AcousticFields:
|
|
496
|
+
if activeListPath is not None:
|
|
497
|
+
with open(activeListPath, 'r') as file:
|
|
498
|
+
lines = file.readlines()
|
|
499
|
+
expected_name = lines[self.AcousticFields.index(field)].strip()
|
|
500
|
+
nameField = field.getName_field()
|
|
501
|
+
if nameField.startswith("field_"):
|
|
502
|
+
nameField = nameField[len("field_"):]
|
|
503
|
+
if nameField != expected_name:
|
|
504
|
+
raise ValueError(f"Field name {nameField} does not match the expected name {expected_name} from the active list.")
|
|
505
|
+
print("Experimental AO signals are correctly initialized.")
|
|
@@ -55,20 +55,10 @@ class Experiment(ABC):
|
|
|
55
55
|
"""
|
|
56
56
|
pass
|
|
57
57
|
|
|
58
|
-
def cutAcousticFields(self, max_t
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
elif self.AOsignal_withoutTumor is not None:
|
|
63
|
-
max_t = self.AOsignal_withoutTumor.shape[0] / float(self.params.acoustic['f_saving'])
|
|
64
|
-
else:
|
|
65
|
-
raise ValueError("Neither AOsignal_withTumor nor AOsignal_withoutTumor is generated. Please generate or load one of them first.")
|
|
66
|
-
min_t = 0
|
|
67
|
-
else:
|
|
68
|
-
if max_t is None:
|
|
69
|
-
raise ValueError("max_t must be provided if useAOsignalShape is False.")
|
|
70
|
-
max_t = float(max_t)
|
|
71
|
-
min_t = float(min_t)
|
|
58
|
+
def cutAcousticFields(self, max_t, min_t=0):
|
|
59
|
+
|
|
60
|
+
max_t = float(max_t)
|
|
61
|
+
min_t = float(min_t)
|
|
72
62
|
|
|
73
63
|
min_sample = int(np.floor(min_t * float(self.params.acoustic['f_saving'])))
|
|
74
64
|
max_sample = int(np.floor(max_t * float(self.params.acoustic['f_saving'])))
|
|
@@ -87,14 +77,6 @@ class Experiment(ABC):
|
|
|
87
77
|
raise ValueError(f"Field {field.getName_field()} has an invalid shape: {field.field.shape}. Expected shape to be at least ({max_sample},).")
|
|
88
78
|
self.AcousticFields[i].field = field.field[min_sample:max_sample, :, :]
|
|
89
79
|
|
|
90
|
-
if overWrite:
|
|
91
|
-
if fieldDataPath is None:
|
|
92
|
-
if self.fieldDataPath is None:
|
|
93
|
-
raise ValueError("fieldDataPath must be provided when overWrite is True.")
|
|
94
|
-
else:
|
|
95
|
-
fieldDataPath = self.fieldDataPath
|
|
96
|
-
self.saveAcousticFields(fieldDataPath)
|
|
97
|
-
|
|
98
80
|
def addNoise(self, noiseType='gaussian', noiseLvl=0.1, withTumor=True):
|
|
99
81
|
"""
|
|
100
82
|
Ajoute du bruit (gaussien ou poisson) au signal AO sélectionné.
|
|
@@ -132,7 +114,6 @@ class Experiment(ABC):
|
|
|
132
114
|
noiseSignals[:, i] = noisy_signal
|
|
133
115
|
return noiseSignals
|
|
134
116
|
|
|
135
|
-
|
|
136
117
|
def reduceDims(self, mode='avg'):
|
|
137
118
|
"""
|
|
138
119
|
Réduit les dimensions T, X, Z d'un numpy array (T, X, Z) par 2 en utilisant une convolution.
|
|
@@ -173,6 +154,16 @@ class Experiment(ABC):
|
|
|
173
154
|
for param in ['dx', 'dy', 'dz']:
|
|
174
155
|
convert_and_update(self.params.general, param, lambda x: x * 2)
|
|
175
156
|
|
|
157
|
+
def normalizeAOsignals(self, withTumor=True):
|
|
158
|
+
if withTumor and self.AOsignal_withTumor is None:
|
|
159
|
+
raise ValueError("AO signal with tumor is not generated. Please generate it first.")
|
|
160
|
+
if not withTumor and self.AOsignal_withoutTumor is None:
|
|
161
|
+
raise ValueError("AO signal without tumor is not generated. Please generate it first.")
|
|
162
|
+
if withTumor:
|
|
163
|
+
self.AOsignal_withTumor = self.AOsignal_withTumor - np.min(self.AOsignal_withTumor)/(np.max(self.AOsignal_withTumor)-np.min(self.AOsignal_withTumor))
|
|
164
|
+
else:
|
|
165
|
+
self.AOsignal_withoutTumor = self.AOsignal_withoutTumor - np.min(self.AOsignal_withoutTumor)/(np.max(self.AOsignal_withoutTumor)-np.min(self.AOsignal_withoutTumor))
|
|
166
|
+
|
|
176
167
|
def saveAcousticFields(self, save_directory):
|
|
177
168
|
progress_bar = trange(len(self.AcousticFields), desc="Saving Acoustic Fields")
|
|
178
169
|
for i in progress_bar:
|
|
@@ -249,71 +240,97 @@ class Experiment(ABC):
|
|
|
249
240
|
return ani
|
|
250
241
|
|
|
251
242
|
def generateAOsignal(self, withTumor=True, AOsignalDataPath=None):
|
|
252
|
-
if self.AcousticFields is None:
|
|
253
|
-
raise ValueError("AcousticFields is not initialized. Please generate the system matrix first.")
|
|
254
|
-
|
|
255
|
-
if self.OpticImage is None:
|
|
256
|
-
raise ValueError("OpticImage is not initialized. Please generate the phantom first.")
|
|
257
243
|
|
|
258
244
|
if AOsignalDataPath is not None:
|
|
259
245
|
if not os.path.exists(AOsignalDataPath):
|
|
260
246
|
raise FileNotFoundError(f"AO file {AOsignalDataPath} not found.")
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
247
|
+
if withTumor:
|
|
248
|
+
self.AOsignal_withTumor = self._loadAOSignal(AOsignalDataPath)
|
|
249
|
+
if self.AOsignal_withTumor.shape[0] != self.AcousticFields[0].field.shape[0]:
|
|
250
|
+
print(f"AO signal shape {self.AOsignal_withTumor.shape} does not match the expected shape {self.AcousticFields[0].field.shape}. Resizing Acoustic fields...")
|
|
251
|
+
self.cutAcousticFields(max_t=self.AOsignal_withTumor.shape[0] / float(self.params.acoustic['f_saving']), min_t=0)
|
|
264
252
|
else:
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
253
|
+
self.AOsignal_withoutTumor = self._loadAOSignal(AOsignalDataPath)
|
|
254
|
+
if self.AOsignal_withoutTumor.shape[0] != self.AcousticFields[0].field.shape[0]:
|
|
255
|
+
print(f"AO signal shape {self.AOsignal_withoutTumor.shape} does not match the expected shape {self.AcousticFields[0].field.shape}. Resizing Acoustic fields...")
|
|
256
|
+
self.cutAcousticFields(max_t=self.AOsignal_withoutTumor.shape[0] / float(self.params.acoustic['f_saving']), min_t=0)
|
|
257
|
+
else:
|
|
258
|
+
if self.AcousticFields is None:
|
|
259
|
+
raise ValueError("AcousticFields is not initialized. Please generate the system matrix first.")
|
|
260
|
+
|
|
261
|
+
if self.OpticImage is None:
|
|
262
|
+
raise ValueError("OpticImage is not initialized. Please generate the phantom first.")
|
|
263
|
+
|
|
264
|
+
if not all(field.field.shape == self.AcousticFields[0].field.shape for field in self.AcousticFields):
|
|
265
|
+
minShape = min([field.field.shape[0] for field in self.AcousticFields])
|
|
266
|
+
self.cutAcousticFields(max_t=minShape * self.params['fs_aq'])
|
|
267
|
+
else:
|
|
268
|
+
shape_field = self.AcousticFields[0].field.shape
|
|
279
269
|
|
|
280
|
-
|
|
281
|
-
for t in range(self.AcousticFields[i].field.shape[0]):
|
|
282
|
-
if withTumor:
|
|
283
|
-
interaction = self.OpticImage.phantom * self.AcousticFields[i].field[t, :, :]
|
|
284
|
-
else:
|
|
285
|
-
interaction = self.OpticImage.laser.intensity * self.AcousticFields[i].field[t, :, :]
|
|
286
|
-
AOsignal[t, i] = np.sum(interaction)
|
|
270
|
+
AOsignal = np.zeros((shape_field[0], len(self.AcousticFields)), dtype=np.float32)
|
|
287
271
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
272
|
+
if withTumor:
|
|
273
|
+
description = "Generating AO Signal with Tumor"
|
|
274
|
+
else:
|
|
275
|
+
description = "Generating AO Signal without Tumor"
|
|
276
|
+
|
|
277
|
+
for i in trange(len(self.AcousticFields), desc=description):
|
|
278
|
+
for t in range(self.AcousticFields[i].field.shape[0]):
|
|
279
|
+
if withTumor:
|
|
280
|
+
interaction = self.OpticImage.phantom * self.AcousticFields[i].field[t, :, :]
|
|
281
|
+
else:
|
|
282
|
+
interaction = self.OpticImage.laser.intensity * self.AcousticFields[i].field[t, :, :]
|
|
283
|
+
AOsignal[t, i] = np.sum(interaction)
|
|
284
|
+
|
|
285
|
+
if withTumor:
|
|
286
|
+
self.AOsignal_withTumor = AOsignal
|
|
287
|
+
else:
|
|
288
|
+
self.AOsignal_withoutTumor = AOsignal
|
|
292
289
|
|
|
293
290
|
@staticmethod
|
|
294
291
|
def _loadAOSignal(AOsignalPath):
|
|
295
|
-
# if extension is .cdh load .cdf file
|
|
296
292
|
if AOsignalPath.endswith(".cdh"):
|
|
297
293
|
with open(AOsignalPath, "r") as file:
|
|
298
294
|
cdh_content = file.readlines()
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
295
|
+
|
|
296
|
+
cdf_path = AOsignalPath.replace(".cdh", ".cdf")
|
|
297
|
+
|
|
298
|
+
# Extraire les paramètres depuis le fichier .cdh
|
|
299
|
+
n_scans = int([line.split(":")[1].strip() for line in cdh_content if "Number of events" in line][0])
|
|
300
|
+
n_acquisitions_per_event = int([line.split(":")[1].strip() for line in cdh_content if "Number of acquisitions per event" in line][0])
|
|
301
|
+
num_elements = int([line.split(":")[1].strip() for line in cdh_content if "Number of US transducers" in line][0])
|
|
302
|
+
|
|
303
|
+
# Initialisation des structures
|
|
304
|
+
AO_signal = np.zeros((n_acquisitions_per_event, n_scans), dtype=np.float32)
|
|
305
|
+
active_lists = []
|
|
306
|
+
angles = []
|
|
307
|
+
|
|
308
|
+
# Lecture du fichier binaire
|
|
309
|
+
with open(cdf_path, "rb") as file:
|
|
310
|
+
for j in trange(n_scans, desc="Lecture des événements"):
|
|
311
|
+
# Lire l'activeList : 48 caractères hex = 24 bytes
|
|
312
|
+
active_list_bytes = file.read(24)
|
|
313
|
+
active_list_hex = active_list_bytes.hex()
|
|
314
|
+
active_lists.append(active_list_hex)
|
|
315
|
+
|
|
316
|
+
# Lire l'angle (1 byte signé)
|
|
317
|
+
angle_byte = file.read(1)
|
|
318
|
+
angle = np.frombuffer(angle_byte, dtype=np.int8)[0]
|
|
319
|
+
angles.append(angle)
|
|
320
|
+
|
|
321
|
+
# Lire le signal AO (float32)
|
|
322
|
+
data = np.frombuffer(file.read(n_acquisitions_per_event * 4), dtype=np.float32)
|
|
323
|
+
if len(data) != n_acquisitions_per_event:
|
|
324
|
+
raise ValueError(f"Erreur à l'événement {j} : attendu {n_acquisitions_per_event}, obtenu {len(data)}")
|
|
325
|
+
AO_signal[:, j] = data
|
|
326
|
+
|
|
327
|
+
return AO_signal
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
elif AOsignalPath.endswith(".npy"):
|
|
331
|
+
return np.load(AOsignalPath) # Supposé déjà au bon format
|
|
332
|
+
else:
|
|
333
|
+
raise ValueError("Format de fichier non supporté. Utilisez .cdh/.cdf ou .npy.")
|
|
317
334
|
|
|
318
335
|
def saveAOsignals_Castor(self, save_directory, withTumor=True):
|
|
319
336
|
if withTumor:
|
|
@@ -341,10 +358,10 @@ class Experiment(ABC):
|
|
|
341
358
|
header_content = (
|
|
342
359
|
f"Data filename: {'AOSignals_withTumor.cdf' if withTumor else 'AOSignals_withoutTumor.cdf'}\n"
|
|
343
360
|
f"Number of events: {nScan}\n"
|
|
344
|
-
f"Number of acquisitions per event: {AO_signal.shape[
|
|
361
|
+
f"Number of acquisitions per event: {AO_signal.shape[0]}\n"
|
|
345
362
|
f"Start time (s): 0\n"
|
|
346
363
|
f"Duration (s): 1\n"
|
|
347
|
-
f"Acquisition frequency (Hz): {
|
|
364
|
+
f"Acquisition frequency (Hz): {self.params.acoustic['f_saving']}\n"
|
|
348
365
|
f"Data mode: histogram\n"
|
|
349
366
|
f"Data type: AOT\n"
|
|
350
367
|
f"Number of US transducers: {self.params.acoustic['num_elements']}"
|
{aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py
RENAMED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from AOT_biomaps.AOT_Recon.ReconEnums import PotentialType
|
|
2
|
-
from AOT_biomaps.AOT_Recon.ReconTools import _build_adjacency_sparse
|
|
2
|
+
from AOT_biomaps.AOT_Recon.ReconTools import _build_adjacency_sparse, calculate_memory_requirement, check_gpu_memory
|
|
3
3
|
from AOT_biomaps.Config import config
|
|
4
|
+
|
|
5
|
+
import warnings
|
|
4
6
|
import numpy as np
|
|
5
7
|
import torch
|
|
6
8
|
from tqdm import trange
|
|
@@ -11,9 +13,42 @@ if config.get_process() == 'gpu':
|
|
|
11
13
|
except ImportError:
|
|
12
14
|
raise ImportError("torch_scatter and torch_sparse are required for GPU processing. Please install them using 'pip install torch-scatter torch-sparse' with correct link (follow instructions https://github.com/LucasDuclos/AcoustoOpticTomography/edit/main/README.md).")
|
|
13
15
|
|
|
14
|
-
def
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
def DEPIERRO(
|
|
17
|
+
SMatrix,
|
|
18
|
+
y,
|
|
19
|
+
numIterations,
|
|
20
|
+
beta,
|
|
21
|
+
sigma,
|
|
22
|
+
isSavingEachIteration,
|
|
23
|
+
withTumor,
|
|
24
|
+
max_saves,
|
|
25
|
+
show_logs):
|
|
26
|
+
"""
|
|
27
|
+
This method implements the DEPIERRO algorithm using either CPU or single-GPU PyTorch acceleration.
|
|
28
|
+
Multi-GPU and Multi-CPU modes are not implemented for this algorithm.
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
tumor_str = "WITH" if withTumor else "WITHOUT"
|
|
32
|
+
# Auto-select device and method
|
|
33
|
+
if device is None:
|
|
34
|
+
if torch.cuda.is_available() and check_gpu_memory(config.select_best_gpu(), calculate_memory_requirement(SMatrix, y), show_logs=show_logs):
|
|
35
|
+
device = torch.device(f"cuda:{config.select_best_gpu()}")
|
|
36
|
+
use_gpu = True
|
|
37
|
+
else:
|
|
38
|
+
device = torch.device("cpu")
|
|
39
|
+
use_gpu = False
|
|
40
|
+
else:
|
|
41
|
+
use_gpu = device.type == "cuda"
|
|
42
|
+
# Dispatch to the appropriate implementation
|
|
43
|
+
if use_gpu:
|
|
44
|
+
return _DEPIERRO_GPU(SMatrix, y, numIterations, beta, sigma, isSavingEachIteration, tumor_str, device, max_saves, show_logs)
|
|
45
|
+
else:
|
|
46
|
+
return _DEPIERRO_CPU(SMatrix, y, numIterations, beta, sigma, isSavingEachIteration, tumor_str, device, max_saves, show_logs)
|
|
47
|
+
except Exception as e:
|
|
48
|
+
print(f"Error in MLEM: {type(e).__name__}: {e}")
|
|
49
|
+
return None, None
|
|
50
|
+
|
|
51
|
+
def _DEPIERRO_GPU(SMatrix, y, numIterations, beta, sigma, isSavingEachIteration, tumor_str, device, max_saves, show_logs=True):
|
|
17
52
|
# Conversion des données en tenseurs PyTorch (float64)
|
|
18
53
|
A_matrix_torch = torch.tensor(SMatrix, dtype=torch.float64, device=device)
|
|
19
54
|
y_torch = torch.tensor(y, dtype=torch.float64, device=device)
|
|
@@ -33,7 +68,7 @@ def _DEPIERRO_GPU(SMatrix, y, numIterations, beta, sigma, isSavingEachIteration,
|
|
|
33
68
|
# Construction de la matrice d'adjacence
|
|
34
69
|
adj_index, adj_values = _build_adjacency_sparse(Z, X, device=device, dtype=torch.float64)
|
|
35
70
|
# Description pour la barre de progression
|
|
36
|
-
description = f"AOT-BioMaps -- Bayesian Reconstruction Tomography: DE PIERRO (Sparse QUADRATIC β:{beta:.4f}, σ:{sigma:.4f}) ---- {
|
|
71
|
+
description = f"AOT-BioMaps -- Bayesian Reconstruction Tomography: DE PIERRO (Sparse QUADRATIC β:{beta:.4f}, σ:{sigma:.4f}) ---- {tumor_str} TUMOR ---- processing on single GPU no.{torch.cuda.current_device()}"
|
|
37
72
|
# Configuration pour la sauvegarde des itérations
|
|
38
73
|
saved_indices = [0]
|
|
39
74
|
|
|
@@ -47,7 +82,8 @@ def _DEPIERRO_GPU(SMatrix, y, numIterations, beta, sigma, isSavingEachIteration,
|
|
|
47
82
|
save_indices.append(numIterations - 1)
|
|
48
83
|
|
|
49
84
|
# Boucle principale MAP-EM
|
|
50
|
-
|
|
85
|
+
iterator = trange(numIterations, desc=description) if show_logs else range(numIterations)
|
|
86
|
+
for it in iterator:
|
|
51
87
|
theta_p = matrix_theta_torch[-1]
|
|
52
88
|
theta_p_flat = theta_p.reshape(-1)
|
|
53
89
|
# Étape 1 : Projection avant
|
|
@@ -92,10 +128,8 @@ def _DEPIERRO_GPU(SMatrix, y, numIterations, beta, sigma, isSavingEachIteration,
|
|
|
92
128
|
else:
|
|
93
129
|
return matrix_theta_torch[-1].cpu().numpy(), None
|
|
94
130
|
|
|
95
|
-
def _DEPIERRO_CPU(SMatrix, y,
|
|
131
|
+
def _DEPIERRO_CPU(SMatrix, y, numIterations, beta, sigma, isSavingEachIteration, tumor_str, device, max_saves, show_logs=True):
|
|
96
132
|
try:
|
|
97
|
-
if Omega != PotentialType.QUADRATIC:
|
|
98
|
-
raise ValueError("Depierro95 optimizer only supports QUADRATIC potential function.")
|
|
99
133
|
if beta is None or sigma is None:
|
|
100
134
|
raise ValueError("Depierro95 optimizer requires beta and sigma parameters.")
|
|
101
135
|
|
|
@@ -122,9 +156,10 @@ def _DEPIERRO_CPU(SMatrix, y, Omega, numIterations, beta, sigma, isSavingEachIte
|
|
|
122
156
|
if save_indices[-1] != numIterations - 1:
|
|
123
157
|
save_indices.append(numIterations - 1)
|
|
124
158
|
|
|
125
|
-
description = f"AOT-BioMaps -- Bayesian Reconstruction Tomography: DE PIERRO (Sparse QUADRATIC β:{beta:.4f}, σ:{sigma:.4f}) ---- {
|
|
159
|
+
description = f"AOT-BioMaps -- Bayesian Reconstruction Tomography: DE PIERRO (Sparse QUADRATIC β:{beta:.4f}, σ:{sigma:.4f}) ---- {tumor_str} TUMOR ---- processing on single CPU ----"
|
|
126
160
|
|
|
127
|
-
|
|
161
|
+
iterator = trange(numIterations, desc=description) if show_logs else range(numIterations)
|
|
162
|
+
for it in iterator:
|
|
128
163
|
theta_p = matrix_theta[-1]
|
|
129
164
|
theta_p_flat = theta_p.reshape(-1)
|
|
130
165
|
q_flat = np.dot(A_flat, theta_p_flat)
|
|
@@ -143,9 +178,9 @@ def _DEPIERRO_CPU(SMatrix, y, Omega, numIterations, beta, sigma, isSavingEachIte
|
|
|
143
178
|
theta_p_plus_1_flat = np.clip(theta_p_plus_1_flat, a_min=0, a_max=None)
|
|
144
179
|
theta_next = theta_p_plus_1_flat.reshape(Z, X)
|
|
145
180
|
matrix_theta[-1] = theta_next
|
|
146
|
-
if isSavingEachIteration and
|
|
181
|
+
if isSavingEachIteration and it in save_indices:
|
|
147
182
|
I_reconMatrix.append(theta_next.copy())
|
|
148
|
-
saved_indices.append(
|
|
183
|
+
saved_indices.append(it)
|
|
149
184
|
|
|
150
185
|
if isSavingEachIteration:
|
|
151
186
|
return I_reconMatrix, saved_indices
|