AOT-biomaps 2.9.323__py3-none-any.whl → 2.9.339__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.

@@ -58,3 +58,22 @@ def convert_to_hex_list(matrix):
58
58
  # 5. Assemblage des chaînes (de l'élément N vers 0 pour l'ordre Shift Register standard)
59
59
  return ["".join(hex_matrix[::-1, col]) for col in range(n_scans)]
60
60
 
61
+ def hex_to_binary_profile(hex_string, n_piezos=192):
62
+ hex_string = hex_string.strip().replace(" ", "").replace("\n", "")
63
+ if set(hex_string.lower()) == {'f'}:
64
+ return np.ones(n_piezos, dtype=int)
65
+
66
+ try:
67
+ n_char = len(hex_string)
68
+ n_bits = n_char * 4
69
+ binary_str = bin(int(hex_string, 16))[2:].zfill(n_bits)
70
+ if len(binary_str) < n_piezos:
71
+ # Tronquer/padder en fonction de la taille réelle de la sonde
72
+ binary_str = binary_str.ljust(n_piezos, '0')
73
+ elif len(binary_str) > n_piezos:
74
+ binary_str = binary_str[:n_piezos]
75
+ return np.array([int(b) for b in binary_str])
76
+ except ValueError:
77
+ return np.zeros(n_piezos, dtype=int)
78
+
79
+
@@ -2,19 +2,22 @@ from ._mainExperiment import Experiment
2
2
  from AOT_biomaps.AOT_Acoustic.AcousticEnums import WaveType
3
3
  from AOT_biomaps.AOT_Acoustic.StructuredWave import StructuredWave
4
4
  from AOT_biomaps.Config import config
5
- from AOT_biomaps.AOT_Experiment.ExperimentTools import calc_mat_os, convert_to_hex_list
5
+ from AOT_biomaps.AOT_Experiment.ExperimentTools import calc_mat_os, convert_to_hex_list, hex_to_binary_profile
6
6
  import os
7
7
  import psutil
8
8
  import numpy as np
9
9
  import matplotlib.pyplot as plt
10
10
  from tqdm import trange
11
11
  import h5py
12
- from scipy.io import loadmat
12
+ from scipy.io import loadmat, savemat
13
+
13
14
 
14
15
  class Tomography(Experiment):
15
16
  def __init__(self, **kwargs):
16
17
  super().__init__(**kwargs)
17
18
  self.patterns = None
19
+ self.theta = []
20
+ self.decimations = []
18
21
 
19
22
  # PUBLIC METHODS
20
23
  def check(self):
@@ -61,6 +64,25 @@ class Tomography(Experiment):
61
64
  """
62
65
  if self.TypeAcoustic.value == WaveType.StructuredWave.value:
63
66
  self.AcousticFields = self._generateAcousticFields_STRUCT_CPU(fieldDataPath, show_log, nameBlock)
67
+ for i in range(len(self.AcousticFields)):
68
+ profile = hex_to_binary_profile(self.AcousticFields[i].getName_field()[6:-4], self.params.acoustic['num_elements'])
69
+ angle = self.AcousticFields[i].angle
70
+ self.theta.append(angle)
71
+
72
+ if set(self.AcousticFields[i].getName_field()[6:-4].lower().replace(" ", "")) == {'f'}:
73
+ fs_key = 0.0 # fs_key est en mm^-1 (0.0 mm^-1)
74
+ else:
75
+ ft_prof = np.fft.fft(profile)
76
+ idx_max = np.argmax(np.abs(ft_prof[1:len(profile)//2])) + 1
77
+ freqs = np.fft.fftfreq(len(profile), d=self.params.general['dx'])
78
+
79
+ # freqs est en m^-1 car delta_x est en mètres.
80
+ fs_m_inv = abs(freqs[idx_max])
81
+
82
+ fs_key = fs_m_inv # Fréquence spatiale en mm^-1
83
+
84
+ # fs = n * dfx => n = fs / dfx with dfx = 1/(N*delta_x)
85
+ self.decimations.append(int(fs_key / (1/(len(profile)*self.params.general['dx']))))
64
86
  else:
65
87
  raise ValueError("Unsupported wave type.")
66
88
 
@@ -283,15 +305,29 @@ class Tomography(Experiment):
283
305
  list: Liste de strings au format "hex_angle".
284
306
  """
285
307
  if decimations is not None and angles is not None:
286
- self._genereate_patterns_from_decimations(decimations, angles)
308
+ self.patterns = self._genereate_patterns_from_decimations(decimations, angles)
287
309
  elif N is not None and N > 1:
288
310
  self.patterns = self._generate_patterns(N)
311
+ if not self._check_patterns(self.patterns):
312
+ raise ValueError("Generated patterns failed validation.")
289
313
  else:
290
314
  raise ValueError("Either N (>=2) or both decimations and angles must be provided for pattern generation.")
291
- if not self._check_patterns(self.patterns):
292
- raise ValueError("Generated patterns failed validation.")
293
-
294
-
315
+
316
+ def saveAOsignals_matlab(self, filePath):
317
+ ActiveList = []
318
+ DelayLaw = []
319
+ c = self.params.acoustic['c0']
320
+ NbElemts = self.params.acoustic['num_elements']
321
+ pitch = self.params.acoustic['element_width']
322
+
323
+ for i in range(len(self.AcousticFields)):
324
+ profile = hex_to_binary_profile(self.AcousticFields[i].getName_field()[6:-4], NbElemts)
325
+ ActiveList.append(profile)
326
+ angle = self.AcousticFields[i].angle
327
+ Delay = 1000 * (1/c) * np.sin(np.deg2rad(angle)) * np.arange(1, NbElemts + 1) * pitch
328
+ DelayLaw.append(Delay - np.min(Delay))
329
+
330
+ savemat(filePath, {'data': self.AOsignal_withTumor, 'thetas': self.theta, 'decimations': self.decimations, 'ActiveList' : ActiveList, 'DelayLaw': DelayLaw})
295
331
 
296
332
  def selectAngles(self, angles):
297
333
 
@@ -401,6 +437,7 @@ class Tomography(Experiment):
401
437
 
402
438
  # 3. Construction de la liste de dictionnaires
403
439
  patterns = []
440
+ print(f"Generating {Nscans} patterns from decimations and angles...")
404
441
  for i in range(Nscans):
405
442
  # On retrouve l'angle correspondant à l'index i
406
443
  # La logique est cyclique sur la taille de 'angles'
@@ -639,6 +676,7 @@ class Tomography(Experiment):
639
676
  AcousticField.save_field(fieldDataPath)
640
677
  listAcousticFields.append(AcousticField)
641
678
  progress_bar.set_postfix_str("")
679
+
642
680
  return listAcousticFields
643
681
 
644
682
  def load_experimentalAO(self, pathAO, withTumor = True, h5name='AOsignal'):
@@ -1,14 +1,107 @@
1
1
  from ._mainRecon import Recon
2
2
  from .ReconEnums import ReconType, AnalyticType, ProcessType
3
+ from AOT_biomaps.AOT_Experiment.Tomography import hex_to_binary_profile
4
+ from .ReconTools import get_phase_deterministic
3
5
 
4
6
  import numpy as np
5
7
  from tqdm import trange
8
+ import torch
9
+ import tqdm
10
+
6
11
 
7
12
  class AnalyticRecon(Recon):
8
13
  def __init__(self, analyticType, **kwargs):
9
14
  super().__init__(**kwargs)
10
15
  self.reconType = ReconType.Analytic
11
16
  self.analyticType = analyticType
17
+ self.AOsignal_demoldulated = None
18
+
19
+
20
+
21
+ def parse_and_demodulate(self, withTumor=True):
22
+
23
+ if withTumor:
24
+ AOsignal = self.experiment.AOsignal_withTumor
25
+ else:
26
+ AOsignal = self.experiment.AOsignal_withoutTumor
27
+ delta_x = self.experiment.params.general['dx'] # en m
28
+ n_piezos = self.experiment.params.acoustic['num_elements']
29
+ demodulated_data = {}
30
+ structured_buffer = {}
31
+
32
+ for i in trange(len(self.experiment.AcousticFields), desc="Demodulating AO signals"):
33
+ label = self.experiment.AcousticFields[i].getName_field()
34
+
35
+ parts = label.split("_")
36
+ hex_pattern = parts[0]
37
+ angle_code = parts[-1]
38
+
39
+ # Angle
40
+ if angle_code.startswith("1"):
41
+ angle_deg = -int(angle_code[1:])
42
+ else:
43
+ angle_deg = int(angle_code)
44
+ angle_rad = np.deg2rad(angle_deg)
45
+
46
+ # Onde Plane (f_s = 0)
47
+ if set(hex_pattern.lower().replace(" ", "")) == {'f'}:
48
+ fs_key = 0.0 # fs_key est en mm^-1 (0.0 mm^-1)
49
+ demodulated_data[(fs_key, angle_rad)] = np.array(AOsignal[i])
50
+ continue
51
+
52
+ # Onde Structurée
53
+ profile = hex_to_binary_profile(hex_pattern, n_piezos)
54
+
55
+ # Calcul FS (Fréquence de Structuration)
56
+ ft_prof = np.fft.fft(profile)
57
+ # On regarde uniquement la partie positive non DC
58
+ idx_max = np.argmax(np.abs(ft_prof[1:len(profile)//2])) + 1
59
+ freqs = np.fft.fftfreq(len(profile), d=delta_x)
60
+
61
+ # freqs est en m^-1 car delta_x est en mètres.
62
+ fs_m_inv = abs(freqs[idx_max])
63
+
64
+ # *** CORRECTION 1: Conversion de f_s en mm^-1 (mm^-1 est utilisé dans iRadon) ***
65
+ fs_key = fs_m_inv / 1000.0 # Fréquence spatiale en mm^-1
66
+
67
+
68
+ if fs_key == 0: continue
69
+
70
+ # Calcul de la Phase (Shift)
71
+ phase = get_phase_deterministic(profile)
72
+
73
+ # Stockage par (fs, theta) et phase
74
+ key = (fs_key, angle_rad)
75
+ if key not in structured_buffer:
76
+ structured_buffer[key] = {}
77
+
78
+ # La moyenne est nécessaire si plusieurs acquisitions ont la même phase (pour le SNR)
79
+ if phase in structured_buffer[key]:
80
+ structured_buffer[key][phase] = (structured_buffer[key][phase] + np.array(AOsignal[i])) / 2
81
+ else:
82
+ structured_buffer[key][phase] = np.array(AOsignal[i])
83
+
84
+
85
+
86
+ for (fs, theta), phases in structured_buffer.items():
87
+ s0 = phases.get(0.0, 0)
88
+ s_pi_2 = phases.get(np.pi/2, 0)
89
+ s_pi = phases.get(np.pi, 0)
90
+ s_3pi_2 = phases.get(3*np.pi/2, 0)
91
+
92
+ # Assurer que les zéros sont des vecteurs de la bonne taille
93
+ example = next(val for val in phases.values() if not isinstance(val, int))
94
+ if isinstance(s0, int): s0 = np.zeros_like(example)
95
+ if isinstance(s_pi, int): s_pi = np.zeros_like(example)
96
+ if isinstance(s_pi_2, int): s_pi_2 = np.zeros_like(example)
97
+ if isinstance(s_3pi_2, int): s_3pi_2 = np.zeros_like(example)
98
+
99
+ real = s0 - s_pi
100
+ imag = s_pi_2 - s_3pi_2
101
+
102
+ demodulated_data[(fs, theta)] = (real - 1j * imag) / (2/np.pi)
103
+
104
+ return demodulated_data
12
105
 
13
106
  def run(self, processType = ProcessType.PYTHON, withTumor= True):
14
107
  """
@@ -34,6 +127,7 @@ class AnalyticRecon(Recon):
34
127
  analyticType: The type of analytic reconstruction to perform (default is iFOURIER).
35
128
  """
36
129
  if withTumor:
130
+ self.AOsignal_demoldulated = self.parse_and_demodulate(withTumor=True)
37
131
  if self.analyticType == AnalyticType.iFOURIER:
38
132
  self.reconPhantom = self._iFourierRecon(self.experiment.AOsignal_withTumor)
39
133
  elif self.analyticType == AnalyticType.iRADON:
@@ -41,6 +135,7 @@ class AnalyticRecon(Recon):
41
135
  else:
42
136
  raise ValueError(f"Unknown analytic type: {self.analyticType}")
43
137
  else:
138
+ self.AOsignal_demoldulated = self.parse_and_demodulate(withTumor=False)
44
139
  if self.analyticType == AnalyticType.iFOURIER:
45
140
  self.reconLaser = self._iFourierRecon(self.experiment.AOsignal_withoutTumor)
46
141
  elif self.analyticType == AnalyticType.iRADON:
@@ -6,6 +6,7 @@ import pycuda.driver as drv
6
6
  from numba import njit, prange
7
7
  from torch_sparse import coalesce
8
8
  from scipy.signal.windows import hann
9
+ from itertools import groupby
9
10
 
10
11
  def load_recon(hdr_path):
11
12
  """
@@ -483,4 +484,48 @@ def power_method_estimate_L__SELL(SMatrix, stream, n_it=20, block_size=256):
483
484
  g.free()
484
485
  except:
485
486
  pass
486
- return max(L_sq, 1e-6)
487
+ return max(L_sq, 1e-6)
488
+
489
+ def get_phase_deterministic(profile):
490
+ """
491
+ Détermine la phase en se basant sur la valeur initiale (0 ou 1) et l'état
492
+ de décalage (is_shifted) de la séquence binaire.
493
+
494
+ ATTENTION: Cette fonction est conservée mais la logique est souvent simplifiée
495
+ en pratique si les labels garantissent les phases 0, pi/2, pi, 3pi/2.
496
+ """
497
+ runs = [(k, sum(1 for _ in g)) for k, g in groupby(profile)]
498
+ if not runs: return 0.0
499
+
500
+ nominal_half_period = max([r[1] for r in runs])
501
+ if nominal_half_period == 0: return 0.0
502
+
503
+ first_val = runs[0][0] # 0 ou 1
504
+ first_len = runs[0][1]
505
+ # Détection de cycle 50%
506
+ is_shifted = (0.3 < first_len / nominal_half_period < 0.7)
507
+
508
+ # --- LOGIQUE DE MAPPAGE DE PHASE SIMPLIFIÉE (idx 1 à 4) ---
509
+
510
+ if first_val == 0:
511
+ if is_shifted:
512
+ idx = 3 # C1/C3 décalé (phi_1 ou phi_3)
513
+ else:
514
+ idx = 4 # C2/C4 non décalé
515
+ else: # first_val == 1
516
+ if is_shifted:
517
+ idx = 1 # C1/C3 décalé (phi_1 ou phi_3)
518
+ else:
519
+ idx = 2 # C2/C4 non décalé
520
+
521
+ # On utilise les phases de quadrature 0, pi/2, pi, 3pi/2
522
+ if idx == 1:
523
+ phase = 0
524
+ elif idx == 2 :
525
+ phase = np.pi/2
526
+ elif idx == 3 :
527
+ phase = np.pi
528
+ elif idx == 4 :
529
+ phase = 3*np.pi/2
530
+
531
+ return phase
AOT_biomaps/__init__.py CHANGED
@@ -85,7 +85,7 @@ from .AOT_Recon.AOT_PotentialFunctions.RelativeDifferences import *
85
85
  from .Config import config
86
86
  from .Settings import *
87
87
 
88
- __version__ = '2.9.323'
88
+ __version__ = '2.9.339'
89
89
  __process__ = config.get_process()
90
90
 
91
91
  def initialize(process=None):
@@ -170,6 +170,22 @@ def initialize(process=None):
170
170
 
171
171
 
172
172
 
173
+
174
+
175
+
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
+
184
+
185
+
186
+
187
+
188
+
173
189
 
174
190
 
175
191
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AOT_biomaps
3
- Version: 2.9.323
3
+ Version: 2.9.339
4
4
  Summary: Acousto-Optic Tomography
5
5
  Home-page: https://github.com/LucasDuclos/AcoustoOpticTomography
6
6
  Author: Lucas Duclos
@@ -1,6 +1,6 @@
1
1
  AOT_biomaps/Config.py,sha256=ghEOP1n8aO1pR-su13wMeAZAxZRfry5hH67NbtZ8SqI,3614
2
2
  AOT_biomaps/Settings.py,sha256=v8fPhnvvcfBJP29m1RLOTEr3jndGLGwbUiORXmsj2Bo,2853
3
- AOT_biomaps/__init__.py,sha256=CzNrJamLw5SZZ9fGeXRnGrBUqSj90vh2wZx1792YWto,4308
3
+ AOT_biomaps/__init__.py,sha256=r0H6FXsW0n-ethz4CgAmwp24IOM9OaxvBLKxLkZ0We0,4340
4
4
  AOT_biomaps/AOT_Acoustic/AcousticEnums.py,sha256=s5kXa6jKzbS4btwbubrVcynLOr0yg5tth5vL_FGfbMk,1802
5
5
  AOT_biomaps/AOT_Acoustic/AcousticTools.py,sha256=h2sCtGVcDtyLtEF1q7sLZmuWivWmesVGUBPnW-ndQqc,7535
6
6
  AOT_biomaps/AOT_Acoustic/FocusedWave.py,sha256=3kGKKDx_3Msy5COYqIwzROPORGWvNjw8UsDanBfkMXE,11037
@@ -9,9 +9,9 @@ AOT_biomaps/AOT_Acoustic/PlaneWave.py,sha256=xza-rj5AUWDecLkGDxRcULrwZVWeBvGnEP2
9
9
  AOT_biomaps/AOT_Acoustic/StructuredWave.py,sha256=jTLVlOhYLWJb5MxZPxhq3OFVlz2McoyMPBmfLvnekDU,18209
10
10
  AOT_biomaps/AOT_Acoustic/__init__.py,sha256=t9M2rRqa_L9pk7W2FeELTkHEMuP4DBr4gBRldMqsQbg,491
11
11
  AOT_biomaps/AOT_Acoustic/_mainAcoustic.py,sha256=RdmhRF1i0KAlpsP7_wnZ7F4J27br3eUc4XR91Qq7C64,44158
12
- AOT_biomaps/AOT_Experiment/ExperimentTools.py,sha256=BkHSX_foyyj5UrHZWQH5F9DGeV8o2fkp3euEbcvE4vA,2399
12
+ AOT_biomaps/AOT_Experiment/ExperimentTools.py,sha256=EyTIwgxTK-FqJYlhdjgirfWCSL1kTp-IOS0tTgiAVNA,3153
13
13
  AOT_biomaps/AOT_Experiment/Focus.py,sha256=B2nBawmv-NG2AWJx9zgQ8GlN6aFB9FwTSqX-M-phKXg,3193
14
- AOT_biomaps/AOT_Experiment/Tomography.py,sha256=-7HfRDPchTRwxm0SObU_pGN3FH4HN6b1WJvLuacGzFU,34672
14
+ AOT_biomaps/AOT_Experiment/Tomography.py,sha256=3JlslI9GNZy-u0uYgf0u9qyaVAQki6cYTHhcGK33aFg,36747
15
15
  AOT_biomaps/AOT_Experiment/__init__.py,sha256=H9zMLeBLA6uhbaHohAa-2u5mDDxqJi8oE5c6tShdQp8,308
16
16
  AOT_biomaps/AOT_Experiment/_mainExperiment.py,sha256=zSfuNrsz7nhiKrGIdK6CAXjlI2T6qYC5-JXHFgPNzhc,24674
17
17
  AOT_biomaps/AOT_Optic/Absorber.py,sha256=jEodzRy7gkEH-wbazVasRQiri0dU16BfapmR-qnTSvM,867
@@ -21,12 +21,12 @@ AOT_biomaps/AOT_Optic/__init__.py,sha256=HSUVhfz0NzwHHZZ9KP9Xyfu33IgP_rYJX86J-gE
21
21
  AOT_biomaps/AOT_Optic/_mainOptic.py,sha256=Wk63CcgWbU-ygMfjNK80islaUbGGJpTXgZY3_C2KQNY,8179
22
22
  AOT_biomaps/AOT_Recon/AOT_biomaps_kernels.cubin,sha256=JWy-bdtBTZdnNlDbJGZKwXyF-2u1wICtmlOC_YxEL6o,82528
23
23
  AOT_biomaps/AOT_Recon/AlgebraicRecon.py,sha256=CGBXZyYEZ3TOTFOKSt-h7NGuFbuI9PNr3YTWTbSLxDo,46832
24
- AOT_biomaps/AOT_Recon/AnalyticRecon.py,sha256=RaQ5AJ1HUmSct0BgjZ0GWSJg7SALCn3Q0laqj1yyhAE,7123
24
+ AOT_biomaps/AOT_Recon/AnalyticRecon.py,sha256=NKO7nE2Roq3E9HzMf_eJobm7rNQK1xzaQ0W9zXic9CY,11067
25
25
  AOT_biomaps/AOT_Recon/BayesianRecon.py,sha256=RnnPa-tTcvirwiNPnCRZnSM4NWeEEltYET-piBbp34g,12671
26
26
  AOT_biomaps/AOT_Recon/DeepLearningRecon.py,sha256=RfVcEsi4GeGqJn0_SPxwQPQx6IQjin79WKh2UarMRLI,1383
27
27
  AOT_biomaps/AOT_Recon/PrimalDualRecon.py,sha256=JbFhxiyUoSTnlJgHbOWIfUUwhwfZoi39RJMnfkagegY,16504
28
28
  AOT_biomaps/AOT_Recon/ReconEnums.py,sha256=KAf55RqHAr2ilt6pxFrUBGQOn-7HA8NP6TyL-1FNiXo,19714
29
- AOT_biomaps/AOT_Recon/ReconTools.py,sha256=py1zKVEa0j7EfmcNZS2lpVQwzlkY6rRWsDQ8izWlme4,19872
29
+ AOT_biomaps/AOT_Recon/ReconTools.py,sha256=-ZbzRHSzUprjzPRGCJeBiow_2AEvS2IzCSrv3XfzpLs,21307
30
30
  AOT_biomaps/AOT_Recon/__init__.py,sha256=xs_argJqXKFl76xP7-jiUc1ynOEEtY7XZ0gDxD5uVZc,246
31
31
  AOT_biomaps/AOT_Recon/_mainRecon.py,sha256=exoa2UBMfMHjemxAU9dW0mhEfsP6Oe1qjSfrTrgbIcY,13125
32
32
  AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py,sha256=qA1n722GLQJH3V8HcLr5q_GxEwBS_NRlIT3E6JZk-Ag,9479
@@ -42,7 +42,7 @@ AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/__init__.py,sha256=RwrJdLOFbAFBFnRx
42
42
  AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_CSR.py,sha256=RACc2P5oxmp0uPLAGnNj9mEtAxa_OlepNgCawKij3jI,12062
43
43
  AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_SELL.py,sha256=ti3dZQsb_Uu62C7Bn65Z-yf-R5NKCFsmnBT5GlLd_HY,15138
44
44
  AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/__init__.py,sha256=8nou-hqjQjuCTLhoL5qv4EM_lMPFviAZAZKSPhi84jE,67
45
- aot_biomaps-2.9.323.dist-info/METADATA,sha256=9n72wEg-tUeIC9EWY6rFRJCpFwBOnly-3IQZSVdvLts,700
46
- aot_biomaps-2.9.323.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
- aot_biomaps-2.9.323.dist-info/top_level.txt,sha256=6STF-lT4kaAnBHJYCripmN5mZABoHjMuY689JdiDphk,12
48
- aot_biomaps-2.9.323.dist-info/RECORD,,
45
+ aot_biomaps-2.9.339.dist-info/METADATA,sha256=3XoTP8HukgXdJdBPQC66FjZ5q-SvOn5LhVETzFDXvfY,700
46
+ aot_biomaps-2.9.339.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
+ aot_biomaps-2.9.339.dist-info/top_level.txt,sha256=6STF-lT4kaAnBHJYCripmN5mZABoHjMuY689JdiDphk,12
48
+ aot_biomaps-2.9.339.dist-info/RECORD,,