foscat 3.8.0__tar.gz → 3.8.2__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.
Files changed (35) hide show
  1. {foscat-3.8.0/src/foscat.egg-info → foscat-3.8.2}/PKG-INFO +1 -1
  2. {foscat-3.8.0 → foscat-3.8.2}/pyproject.toml +1 -1
  3. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/BkBase.py +10 -1
  4. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/BkNumpy.py +20 -2
  5. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/BkTensorflow.py +11 -2
  6. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/BkTorch.py +20 -3
  7. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/FoCUS.py +1 -1
  8. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/alm.py +10 -11
  9. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/scat_cov.py +46 -33
  10. {foscat-3.8.0 → foscat-3.8.2/src/foscat.egg-info}/PKG-INFO +1 -1
  11. {foscat-3.8.0 → foscat-3.8.2}/LICENSE +0 -0
  12. {foscat-3.8.0 → foscat-3.8.2}/README.md +0 -0
  13. {foscat-3.8.0 → foscat-3.8.2}/setup.cfg +0 -0
  14. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/CNN.py +0 -0
  15. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/CircSpline.py +0 -0
  16. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/GCNN.py +0 -0
  17. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/Softmax.py +0 -0
  18. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/Spline1D.py +0 -0
  19. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/Synthesis.py +0 -0
  20. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/__init__.py +0 -0
  21. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/backend.py +0 -0
  22. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/backend_tens.py +0 -0
  23. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/loss_backend_tens.py +0 -0
  24. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/loss_backend_torch.py +0 -0
  25. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/scat.py +0 -0
  26. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/scat1D.py +0 -0
  27. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/scat2D.py +0 -0
  28. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/scat_cov1D.py +0 -0
  29. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/scat_cov2D.py +0 -0
  30. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/scat_cov_map.py +0 -0
  31. {foscat-3.8.0 → foscat-3.8.2}/src/foscat/scat_cov_map2D.py +0 -0
  32. {foscat-3.8.0 → foscat-3.8.2}/src/foscat.egg-info/SOURCES.txt +0 -0
  33. {foscat-3.8.0 → foscat-3.8.2}/src/foscat.egg-info/dependency_links.txt +0 -0
  34. {foscat-3.8.0 → foscat-3.8.2}/src/foscat.egg-info/requires.txt +0 -0
  35. {foscat-3.8.0 → foscat-3.8.2}/src/foscat.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: foscat
3
- Version: 3.8.0
3
+ Version: 3.8.2
4
4
  Summary: Generate synthetic Healpix or 2D data using Cross Scattering Transform
5
5
  Author-email: Jean-Marc DELOUIS <jean.marc.delouis@ifremer.fr>
6
6
  Maintainer-email: Theo Foulquier <theo.foulquier@ifremer.fr>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "foscat"
3
- version = "3.8.0"
3
+ version = "3.8.2"
4
4
  description = "Generate synthetic Healpix or 2D data using Cross Scattering Transform"
5
5
  readme = "README.md"
6
6
  license = { text = "BSD-3-Clause" }
@@ -539,7 +539,16 @@ class BackendBase:
539
539
  def bk_constant(self,x):
540
540
  raise NotImplementedError("This is an abstract class.")
541
541
 
542
- def bk_empty(self,x):
542
+ def bk_cos(self,x):
543
+ raise NotImplementedError("This is an abstract class.")
544
+
545
+ def bk_sin(self,x):
546
+ raise NotImplementedError("This is an abstract class.")
547
+
548
+ def bk_arctan2(self,c,s):
549
+ raise NotImplementedError("This is an abstract class.")
550
+
551
+ def bk_empty(self,list):
543
552
  raise NotImplementedError("This is an abstract class.")
544
553
 
545
554
  def to_numpy(self,x):
@@ -284,8 +284,17 @@ class BkNumpy(BackendBase.BackendBase):
284
284
  def bk_zeros(self, shape, dtype=None):
285
285
  return np.zeros(shape, dtype=dtype)
286
286
 
287
- def bk_gather(self, data, idx):
288
- return data[idx]
287
+ def bk_gather(self, data, idx,axis=0):
288
+ if axis==0:
289
+ return data[idx]
290
+ elif axis==1:
291
+ return data[:,idx]
292
+ elif axis==2:
293
+ return data[:,:,idx]
294
+ elif axis==3:
295
+ return data[:,:,:,idx]
296
+ return data[:,:,:,:,idx]
297
+
289
298
 
290
299
  def bk_reverse(self, data, axis=0):
291
300
  return np.reverse(data, axis=axis)
@@ -367,6 +376,15 @@ class BkNumpy(BackendBase.BackendBase):
367
376
 
368
377
  return self.bk_cast(x)
369
378
 
379
+ def bk_cos(self,x):
380
+ return self.backend.cos(x)
381
+
382
+ def bk_sin(self,x):
383
+ return self.backend.sin(x)
384
+
385
+ def bk_arctan2(self,c,s):
386
+ return self.backend.arctan2(c,s)
387
+
370
388
  def bk_empty(self,list):
371
389
  return self.backend.empty(list)
372
390
 
@@ -363,8 +363,8 @@ class BkTensorflow(BackendBase.BackendBase):
363
363
  def bk_zeros(self, shape, dtype=None):
364
364
  return self.backend.zeros(shape, dtype=dtype)
365
365
 
366
- def bk_gather(self, data, idx):
367
- return self.backend.gather(data, idx)
366
+ def bk_gather(self, data, idx,axis=0):
367
+ return self.backend.gather(data, idx,axis=axis)
368
368
 
369
369
  def bk_reverse(self, data, axis=0):
370
370
  return self.backend.reverse(data, axis=[axis])
@@ -469,6 +469,15 @@ class BkTensorflow(BackendBase.BackendBase):
469
469
  def bk_constant(self,x):
470
470
  return self.backend.constant(x)
471
471
 
472
+ def bk_cos(self,x):
473
+ return self.backend.cos(x)
474
+
475
+ def bk_sin(self,x):
476
+ return self.backend.sin(x)
477
+
478
+ def bk_arctan2(self,c,s):
479
+ return self.backend.arctan2(c,s)
480
+
472
481
  def bk_empty(self,list):
473
482
  return self.backend.constant(list)
474
483
 
@@ -282,7 +282,7 @@ class BkTorch(BackendBase.BackendBase):
282
282
  return data.view(shape)
283
283
 
284
284
  def bk_repeat(self, data, nn, axis=0):
285
- return self.backend.repeat(data, nn, axis=axis)
285
+ return self.backend.repeat_interleave(data, repeats=nn, dim=axis)
286
286
 
287
287
  def bk_tile(self, data, nn, axis=0):
288
288
 
@@ -329,8 +329,16 @@ class BkTorch(BackendBase.BackendBase):
329
329
  def bk_zeros(self, shape, dtype=None):
330
330
  return self.backend.zeros(shape, dtype=dtype).to(self.torch_device)
331
331
 
332
- def bk_gather(self, data, idx):
333
- return data[idx]
332
+ def bk_gather(self, data, idx,axis=0):
333
+ if axis==0:
334
+ return data[idx]
335
+ elif axis==1:
336
+ return data[:,idx]
337
+ elif axis==2:
338
+ return data[:,:,idx]
339
+ elif axis==3:
340
+ return data[:,:,:,idx]
341
+ return data[:,:,:,:,idx]
334
342
 
335
343
  def bk_reverse(self, data, axis=0):
336
344
  return self.backend.flip(data, dims=[axis])
@@ -427,6 +435,15 @@ class BkTorch(BackendBase.BackendBase):
427
435
 
428
436
  return self.bk_cast(x)
429
437
 
438
+ def bk_cos(self,x):
439
+ return self.backend.cos(x)
440
+
441
+ def bk_sin(self,x):
442
+ return self.backend.sin(x)
443
+
444
+ def bk_arctan2(self,c,s):
445
+ return self.backend.arctan2(c,s)
446
+
430
447
  def bk_empty(self,list):
431
448
  return self.backend.empty(list)
432
449
 
@@ -36,7 +36,7 @@ class FoCUS:
36
36
  mpi_rank=0,
37
37
  ):
38
38
 
39
- self.__version__ = "3.8.0"
39
+ self.__version__ = "3.8.2"
40
40
  # P00 coeff for normalization for scat_cov
41
41
  self.TMPFILE_VERSION = TMPFILE_VERSION
42
42
  self.P1_dic = None
@@ -615,14 +615,13 @@ class alm:
615
615
 
616
616
  if nest:
617
617
  idx = hp.ring2nest(nside, np.arange(12 * nside**2))
618
- if len(i_im.shape) == 1: # nopol
619
- ft_im = self.comp_tf(
620
- self.backend.bk_gather(l_im, idx), nside, realfft=True
618
+ ft_im = self.comp_tf(
619
+ self.backend.bk_gather(l_im, idx, axis=1), nside, realfft=True
620
+ )
621
+ if map2 is not None:
622
+ ft_im2 = self.comp_tf(
623
+ self.backend.bk_gather(l_map2, idx, axis=1), nside, realfft=True
621
624
  )
622
- if map2 is not None:
623
- ft_im2 = self.comp_tf(
624
- self.backend.bk_gather(l_map2, idx), nside, realfft=True
625
- )
626
625
  else:
627
626
  ft_im = self.comp_tf(l_im, nside, realfft=True)
628
627
  if map2 is not None:
@@ -657,13 +656,13 @@ class alm:
657
656
 
658
657
  if nest:
659
658
  idx = hp.ring2nest(nside, np.arange(12 * nside**2))
660
- l_Q = self.backend.bk_gather(l_im[:,int(do_all_pol)], idx)
661
- l_U = self.backend.bk_gather(l_im[:,1 + int(do_all_pol)], idx)
659
+ l_Q = self.backend.bk_gather(l_im[:,int(do_all_pol)], idx,axis=1)
660
+ l_U = self.backend.bk_gather(l_im[:,1 + int(do_all_pol)], idx,axis=1)
662
661
  ft_im_Pp = self.comp_tf(self.backend.bk_complex(l_Q, l_U), nside)
663
662
  ft_im_Pm = self.comp_tf(self.backend.bk_complex(l_Q, -l_U), nside)
664
663
  if map2 is not None:
665
- l_Q = self.backend.bk_gather(l_map2[:,int(do_all_pol)], idx)
666
- l_U = self.backend.bk_gather(l_map2[:,1 + int(do_all_pol)], idx)
664
+ l_Q = self.backend.bk_gather(l_map2[:,int(do_all_pol)], idx,axis=1)
665
+ l_U = self.backend.bk_gather(l_map2[:,1 + int(do_all_pol)], idx,axis=1)
667
666
  ft_im2_Pp = self.comp_tf(self.backend.bk_complex(l_Q, l_U), nside)
668
667
  ft_im2_Pm = self.comp_tf(self.backend.bk_complex(l_Q, -l_U), nside)
669
668
  else:
@@ -2277,14 +2277,14 @@ class funct(FOC.FoCUS):
2277
2277
  if tmp.S1 is not None:
2278
2278
  nS1 = np.expand_dims(tmp.S1, 0)
2279
2279
  else:
2280
- nS0 = np.expand_dims(tmp.S0.numpy(), 0)
2281
- nS2 = np.expand_dims(tmp.S2.numpy(), 0)
2282
- nS3 = np.expand_dims(tmp.S3.numpy(), 0)
2283
- nS4 = np.expand_dims(tmp.S4.numpy(), 0)
2280
+ nS0 = np.expand_dims(self.backend.to_numpy(tmp.S0), 0)
2281
+ nS2 = np.expand_dims(self.backend.to_numpy(tmp.S2), 0)
2282
+ nS3 = np.expand_dims(self.backend.to_numpy(tmp.S3), 0)
2283
+ nS4 = np.expand_dims(self.backend.to_numpy(tmp.S4), 0)
2284
2284
  if tmp.S3P is not None:
2285
- nS3P = np.expand_dims(tmp.S3P.numpy(), 0)
2285
+ nS3P = np.expand_dims(self.backend.to_numpy(tmp.S3P), 0)
2286
2286
  if tmp.S1 is not None:
2287
- nS1 = np.expand_dims(tmp.S1.numpy(), 0)
2287
+ nS1 = np.expand_dims(self.backend.to_numpy(tmp.S1), 0)
2288
2288
 
2289
2289
  if S0 is None:
2290
2290
  S0 = nS0
@@ -2304,24 +2304,24 @@ class funct(FOC.FoCUS):
2304
2304
  S3P = np.concatenate([S3P, nS3P], 0)
2305
2305
  if tmp.S1 is not None:
2306
2306
  S1 = np.concatenate([S1, nS1], 0)
2307
- sS0 = np.std(S0, 0)
2308
- sS2 = np.std(S2, 0)
2309
- sS3 = np.std(S3, 0)
2310
- sS4 = np.std(S4, 0)
2311
- mS0 = np.mean(S0, 0)
2312
- mS2 = np.mean(S2, 0)
2313
- mS3 = np.mean(S3, 0)
2314
- mS4 = np.mean(S4, 0)
2307
+ sS0 = self.backend.bk_cast(np.std(S0, 0))
2308
+ sS2 = self.backend.bk_cast(np.std(S2, 0))
2309
+ sS3 = self.backend.bk_cast(np.std(S3, 0))
2310
+ sS4 = self.backend.bk_cast(np.std(S4, 0))
2311
+ mS0 = self.backend.bk_cast(np.mean(S0, 0))
2312
+ mS2 = self.backend.bk_cast(np.mean(S2, 0))
2313
+ mS3 = self.backend.bk_cast(np.mean(S3, 0))
2314
+ mS4 = self.backend.bk_cast(np.mean(S4, 0))
2315
2315
  if tmp.S3P is not None:
2316
- sS3P = np.std(S3P, 0)
2317
- mS3P = np.mean(S3P, 0)
2316
+ sS3P = self.backend.bk_cast(np.std(S3P, 0))
2317
+ mS3P = self.backend.bk_cast(np.mean(S3P, 0))
2318
2318
  else:
2319
2319
  sS3P = None
2320
2320
  mS3P = None
2321
2321
 
2322
2322
  if tmp.S1 is not None:
2323
- sS1 = np.std(S1, 0)
2324
- mS1 = np.mean(S1, 0)
2323
+ sS1 = self.backend.bk_cast(np.std(S1, 0))
2324
+ mS1 = self.backend.bk_cast(np.mean(S1, 0))
2325
2325
  else:
2326
2326
  sS1 = None
2327
2327
  mS1 = None
@@ -2375,14 +2375,13 @@ class funct(FOC.FoCUS):
2375
2375
 
2376
2376
  # instead of difference between "opposite" channels use weighted average
2377
2377
  # of cosine and sine contributions using all channels
2378
- angles = (
2378
+ angles = self.backend.bk_cast((
2379
2379
  2 * np.pi * np.arange(self.NORIENT) / self.NORIENT
2380
- ) # shape: (NORIENT,)
2381
- angles = angles.reshape(1, 1, self.NORIENT)
2380
+ ).reshape(1, 1, self.NORIENT)) # shape: (NORIENT,)
2382
2381
 
2383
2382
  # we use cosines and sines as weights for sim
2384
- weighted_cos = self.backend.bk_reduce_mean(sim * np.cos(angles), axis=-1)
2385
- weighted_sin = self.backend.bk_reduce_mean(sim * np.sin(angles), axis=-1)
2383
+ weighted_cos = self.backend.bk_reduce_mean(sim * self.backend.bk_cos(angles), axis=-1)
2384
+ weighted_sin = self.backend.bk_reduce_mean(sim * self.backend.bk_sin(angles), axis=-1)
2386
2385
  # For simplicity, take first element of the batch
2387
2386
  cc = weighted_cos[0]
2388
2387
  ss = weighted_sin[0]
@@ -2402,7 +2401,8 @@ class funct(FOC.FoCUS):
2402
2401
  phase = np.fmod(np.arctan2(ss, cc) + 2 * np.pi, 2 * np.pi)
2403
2402
  else:
2404
2403
  phase = np.fmod(
2405
- np.arctan2(ss.numpy(), cc.numpy()) + 2 * np.pi, 2 * np.pi
2404
+ np.arctan2(self.backend.to_numpy(ss),
2405
+ self.backend.to_numpy(cc)) + 2 * np.pi, 2 * np.pi
2406
2406
  )
2407
2407
 
2408
2408
  # instead of linear interpolation cosine‐based interpolation
@@ -2435,7 +2435,8 @@ class funct(FOC.FoCUS):
2435
2435
 
2436
2436
  sim2 = self.backend.bk_reduce_sum(
2437
2437
  self.backend.bk_reshape(
2438
- mat.reshape(1, mat.shape[0], self.NORIENT * self.NORIENT)
2438
+ self.backend.bk_cast(
2439
+ mat.reshape(1, mat.shape[0], self.NORIENT * self.NORIENT))
2439
2440
  * tmp2,
2440
2441
  [sim.shape[0], cmat[k].shape[0], self.NORIENT, self.NORIENT],
2441
2442
  ),
@@ -2445,10 +2446,10 @@ class funct(FOC.FoCUS):
2445
2446
  sim2 = self.backend.bk_abs(self.convol(sim2, axis=1))
2446
2447
 
2447
2448
  weighted_cos2 = self.backend.bk_reduce_mean(
2448
- sim2 * np.cos(angles), axis=-1
2449
+ sim2 * self.backend.bk_cos(angles), axis=-1
2449
2450
  )
2450
2451
  weighted_sin2 = self.backend.bk_reduce_mean(
2451
- sim2 * np.sin(angles), axis=-1
2452
+ sim2 * self.backend.bk_sin(angles), axis=-1
2452
2453
  )
2453
2454
 
2454
2455
  cc2 = weighted_cos2[0]
@@ -2469,7 +2470,8 @@ class funct(FOC.FoCUS):
2469
2470
  phase2 = np.fmod(np.arctan2(ss2, cc2) + 2 * np.pi, 2 * np.pi)
2470
2471
  else:
2471
2472
  phase2 = np.fmod(
2472
- np.arctan2(ss2.numpy(), cc2.numpy()) + 2 * np.pi, 2 * np.pi
2473
+ np.arctan2(self.backend.to_numpy(ss2),
2474
+ self.backend.to_numpy(cc2)) + 2 * np.pi, 2 * np.pi
2473
2475
  )
2474
2476
 
2475
2477
  phase2_scaled = self.NORIENT * phase2 / (2 * np.pi)
@@ -4936,6 +4938,11 @@ class funct(FOC.FoCUS):
4936
4938
  if S4_criteria is None:
4937
4939
  S4_criteria = 'j2>=j1'
4938
4940
 
4941
+ if self.all_bk_type == "float32":
4942
+ C_ONE=np.complex64(1.0)
4943
+ else:
4944
+ C_ONE=np.complex128(1.0)
4945
+
4939
4946
  # determine jmax and nside corresponding to the input map
4940
4947
  im_shape = data.shape
4941
4948
  if self.use_2D:
@@ -5623,7 +5630,10 @@ class funct(FOC.FoCUS):
5623
5630
  edge_dx = min(4, int(2**j3*dx3*2/M))
5624
5631
  edge_dy = min(4, int(2**j3*dy3*2/N))
5625
5632
  # a normalization change due to the cutoff of frequency space
5626
- fft_factor = 1 /(M3*N3) * (M3*N3/M/N)**2
5633
+ if self.all_bk_type == "float32":
5634
+ fft_factor = np.complex64(1 /(M3*N3) * (M3*N3/M/N)**2)
5635
+ else:
5636
+ fft_factor = np.complex128(1 /(M3*N3) * (M3*N3/M/N)**2)
5627
5637
  for j2 in range(0,j3+1):
5628
5638
  #I1_f2_wf3_small = I1_f_small[:,j2].view(N_image,L,1,M3,N3) * wavelet_f3.view(1,1,L,M3,N3)
5629
5639
  #I1_f2_wf3_2_small = I1_f_small[:,j2].view(N_image,L,1,M3,N3) * wavelet_f3_squared.view(1,1,L,M3,N3)
@@ -5635,21 +5645,24 @@ class funct(FOC.FoCUS):
5635
5645
  if use_ref:
5636
5646
  if normalization=='P11':
5637
5647
  norm_factor_S3 = (ref_S2[:,None,j3,:] * ref_P11[:,j2,j3,:,:]**pseudo_coef)**0.5
5648
+ norm_factor_S3 = self.backend.bk_complex(norm_factor_S3,0*norm_factor_S3)
5638
5649
  elif normalization=='S2':
5639
5650
  norm_factor_S3 = (ref_S2[:,None,j3,:] * ref_S2[:,j2,:,None]**pseudo_coef)**0.5
5651
+ norm_factor_S3 = self.backend.bk_complex(norm_factor_S3,0*norm_factor_S3)
5640
5652
  else:
5641
- norm_factor_S3 = 1.0
5653
+ norm_factor_S3 = C_ONE
5642
5654
  else:
5643
5655
  if normalization=='P11':
5644
5656
  # [N_image,l2,l3,x,y]
5645
5657
  P11_temp = self.backend.bk_reduce_mean((I1_f2_wf3_small.abs()**2),axis=(-2,-1)) * fft_factor
5646
5658
  norm_factor_S3 = (S2[:,None,j3,:] * P11_temp**pseudo_coef)**0.5
5659
+ norm_factor_S3 = self.backend.bk_complex(norm_factor_S3,0*norm_factor_S3)
5647
5660
  elif normalization=='S2':
5648
5661
  norm_factor_S3 = (S2[:,None,j3,None,:] * S2[:,None,j2,:,None]**pseudo_coef)**0.5
5662
+ norm_factor_S3 = self.backend.bk_complex(norm_factor_S3,0*norm_factor_S3)
5649
5663
  else:
5650
- norm_factor_S3 = 1.0
5664
+ norm_factor_S3 = C_ONE
5651
5665
 
5652
- norm_factor_S3 = self.backend.bk_complex(norm_factor_S3,0*norm_factor_S3)
5653
5666
 
5654
5667
  if not edge:
5655
5668
  S3.append(self.backend.bk_reduce_mean(
@@ -5695,7 +5708,7 @@ class funct(FOC.FoCUS):
5695
5708
  P = 1/(((S2[:,j3:j3+1,:,None,None] * S2[:,j2:j2+1,None,:,None] )**(0.5*pseudo_coef)))
5696
5709
  P=self.backend.bk_complex(P,0.0*P)
5697
5710
  else:
5698
- P=self.backend.bk_complex(1.0,0.0)
5711
+ P=C_ONE
5699
5712
 
5700
5713
  for j1 in range(0, j2+1):
5701
5714
  if not edge:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: foscat
3
- Version: 3.8.0
3
+ Version: 3.8.2
4
4
  Summary: Generate synthetic Healpix or 2D data using Cross Scattering Transform
5
5
  Author-email: Jean-Marc DELOUIS <jean.marc.delouis@ifremer.fr>
6
6
  Maintainer-email: Theo Foulquier <theo.foulquier@ifremer.fr>
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes