PyMVP 0.1.9__tar.gz → 0.2.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyMVP
3
- Version: 0.1.9
3
+ Version: 0.2.1
4
4
  Summary: Python package for Moving Vessel Profiler correction and analysis
5
5
  Author: MaximilienWemaere
6
6
  Requires-Python: >=3.10
@@ -36,7 +36,7 @@ from scipy.ndimage import median_filter
36
36
 
37
37
 
38
38
  class Analyzer:
39
- def __init__(self, data_path, output_path=None, subdirs=False, Yorig=1950):
39
+ def __init__(self, Yorig=1950):
40
40
  """
41
41
  Initialize the analyzer with the data path and reference year.
42
42
  Args:
@@ -46,14 +46,13 @@ class Analyzer:
46
46
  """
47
47
  self.Yorig = Yorig
48
48
  self.date_ref = datetime(Yorig, 1, 1)
49
- self.data_path = data_path
50
- self.output_path = output_path if output_path is not None else data_path
51
- self.subdirs = subdirs
52
49
  self.mvp = False
53
50
  self.ctd = False
51
+ self.speed = False
52
+ self.corrected = False
54
53
 
55
54
 
56
- def load_mvp_data(self,delp=[],data_path=None,format='raw',only_new=False):
55
+ def load_mvp_data(self,data_path, delp=[], subdirs=False,format='raw',only_new=False, output_path=None):
57
56
  """
58
57
  Load MVP data from .raw and .log files in the data_path folder.
59
58
  Fills the object attributes with data matrices and associated metadata.
@@ -61,8 +60,9 @@ class Analyzer:
61
60
  delp (list): Indices of profiles to remove from the list (optional).
62
61
  data_path (str): Path to the folder containing MVP files (optional).
63
62
  """
64
- if data_path is not None:
65
- self.data_path = data_path
63
+ self.data_path = data_path
64
+ self.subdirs = subdirs
65
+ self.output_path = output_path
66
66
 
67
67
  if format=='raw':
68
68
  if self.subdirs:
@@ -300,7 +300,7 @@ class Analyzer:
300
300
 
301
301
 
302
302
 
303
- def load_mvp_data_again(self,data_path=None,format='raw',delp=[]):
303
+ def load_mvp_data_again(self,data_path,format='raw',delp=[]):
304
304
  """
305
305
  Load MVP data from .raw and .log files in the data_path folder.
306
306
  Fills the object attributes with data matrices and associated metadata.
@@ -308,14 +308,13 @@ class Analyzer:
308
308
  data_path (str): Path to the folder containing MVP files.
309
309
  delp (list): Indices of profiles to remove from the list (optional).
310
310
  """
311
- if data_path is not None:
312
- self.data_path = data_path
311
+
313
312
 
314
313
  if format=='raw':
315
- files = sorted(filter(os.path.isfile,glob.glob(self.data_path + '*.raw', recursive=True)))
314
+ files = sorted(filter(os.path.isfile,glob.glob(data_path + '*.raw', recursive=True)))
316
315
  elif format=='ncdf':
317
- files = sorted(filter(os.path.isfile,glob.glob(self.data_path + '**/MVP*.nc', recursive=True)))
318
- print('Found ' + str(len(files)) + ' MVP files in the directory: ' + self.data_path)
316
+ files = sorted(filter(os.path.isfile,glob.glob(data_path + '**/MVP*.nc', recursive=True)))
317
+ print('Found ' + str(len(files)) + ' MVP files in the directory: ' + data_path)
319
318
 
320
319
 
321
320
 
@@ -562,12 +561,9 @@ class Analyzer:
562
561
  """
563
562
 
564
563
 
565
- if format=='cnv':
566
- list_of_ctd_files = sorted(filter(os.path.isfile,\
567
- glob.glob(data_path_ctd + '*.cnv')))
568
- elif format=='ncdf':
569
- list_of_ctd_files = sorted(filter(os.path.isfile,\
570
- glob.glob(data_path_ctd + 'CTD'+'*.nc')))
564
+ list_of_ctd_files = sorted(filter(os.path.isfile,\
565
+ glob.glob(data_path_ctd + 'CTD'+'*.nc')))
566
+
571
567
  print('Found ' + str(len(list_of_ctd_files)) + ' CTD files in the directory: ' + data_path_ctd)
572
568
 
573
569
 
@@ -631,11 +627,6 @@ class Analyzer:
631
627
 
632
628
 
633
629
 
634
-
635
-
636
-
637
-
638
-
639
630
  def compute_waterflow(self,horizontal_speed=2,corr=False):
640
631
  """
641
632
  Compute the water flow speed (u,v) from the horizontal speed and the direction of the profiles.
@@ -655,6 +646,7 @@ class Analyzer:
655
646
 
656
647
  self.SPEED_mvp = SPEED_MVP
657
648
  print('Water flow speed computed successfully.')
649
+ self.speed = True
658
650
 
659
651
  def print_profile_metadata(self):
660
652
  """
@@ -938,7 +930,7 @@ class Analyzer:
938
930
  plt.xlabel('Fluorescence, ug/L')
939
931
  plt.ylabel('Pressure, dbar')
940
932
 
941
- def plot_diagramTS_raw(self,id_mvp=None,id_ctd=None,correction=False):
933
+ def plot_diagramTS(self,id_mvp=None,id_ctd=None,correction=False):
942
934
  """
943
935
  Plot the TS diagram (Salinity vs Temperature) for one or more profiles, with isopycnals.
944
936
  Args:
@@ -1234,7 +1226,7 @@ class Analyzer:
1234
1226
  print(f" MVP down: {rmse_cond_down:.4f} S/m (deep: {rmse_cond_down_deep:.4f} S/m)")
1235
1227
  print(f" MVP up: {rmse_cond_up:.4f} S/m (deep: {rmse_cond_up_deep:.4f} S/m)")
1236
1228
 
1237
- def correct_oxygen(self,id_mvp=None,id_ctd=None,num_sample=500,plotting=False,correction=False):
1229
+ def correct_oxygen(self,id_mvp=None,id_ctd=None,num_sample=500,plotting=False,):
1238
1230
  """
1239
1231
  Apply oxygen correction to MVP dissolved oxygen profiles thanks to CTD data.
1240
1232
  Args:
@@ -1295,9 +1287,9 @@ class Analyzer:
1295
1287
  rmse_after_full = np.mean(np.sqrt(np.nanmean((DO_mvp_corr_full_interp - DO_ctd_interp)**2,axis=1)))
1296
1288
  print(f"RMSE after correction (full profile): {rmse_after_full:.4f}")
1297
1289
 
1290
+ self.DO_mvp_raw = self.DO_mvp.copy()
1291
+ self.DO_mvp = DO_mvp_corr_full
1298
1292
 
1299
- if correction:
1300
- self.DO_mvp = DO_mvp_corr_full
1301
1293
 
1302
1294
  if plotting:
1303
1295
 
@@ -1314,7 +1306,6 @@ class Analyzer:
1314
1306
  plt.show()
1315
1307
 
1316
1308
 
1317
-
1318
1309
  def mvp_correction(self,high_cutoff=1,dp=0.1):
1319
1310
 
1320
1311
  T_MVP_corr = []
@@ -1368,7 +1359,7 @@ class Analyzer:
1368
1359
  self.COND_mvp_corr = {i: sublist for i, sublist in enumerate(C_MVP_corr)}
1369
1360
  self.SALT_mvp_corr = {i: sublist for i, sublist in enumerate(S_MVP_corr)}
1370
1361
  self.TIME_mvp_corr = {i: sublist for i, sublist in enumerate(Time_MVP_corr)}
1371
-
1362
+ self.corrected = True
1372
1363
 
1373
1364
  print("MVP profiles corrected.")
1374
1365
 
@@ -1380,6 +1371,11 @@ class Analyzer:
1380
1371
  """
1381
1372
  if not self.ctd:
1382
1373
  raise ValueError("CTD data not loaded.")
1374
+
1375
+ if not self.corrected:
1376
+ raise ValueError("MVP data not corrected. Apply corrections first.")
1377
+
1378
+
1383
1379
 
1384
1380
  if not hasattr(self, 'PRES_mvp_corr'):
1385
1381
  raise ValueError("Corrected MVP data not available. Apply corrections first.")
@@ -1397,8 +1393,9 @@ class Analyzer:
1397
1393
  max_lensalt = max([len(p) for p in self.SALT_mvp_corr.values()])
1398
1394
  SALT_mvp_corr_mat = np.array([list(row) + [np.nan] * (max_lensalt - len(row)) for row in self.SALT_mvp_corr.values()])
1399
1395
 
1400
- max_lenvspd = max([len(p) for p in self.SPEED_mvp_corr.values()])
1401
- SPEED_mvp_corr_mat = np.array([list(row) + [np.nan] * (max_lenvspd - len(row)) for row in self.SPEED_mvp_corr.values()])
1396
+ if self.speed:
1397
+ max_lenvspd = max([len(p) for p in self.SPEED_mvp_corr.values()])
1398
+ SPEED_mvp_corr_mat = np.array([list(row) + [np.nan] * (max_lenvspd - len(row)) for row in self.SPEED_mvp_corr.values()])
1402
1399
 
1403
1400
  max_lentime = max([len(p) for p in self.TIME_mvp_corr.values()])
1404
1401
  TIME_mvp_corr_mat = np.array([list(row) + [np.nan] * (max_lentime - len(row)) for row in self.TIME_mvp_corr.values()])
@@ -1406,22 +1403,123 @@ class Analyzer:
1406
1403
 
1407
1404
  pressure_grid = np.linspace(np.nanmin(PRES_mvp_corr_mat), np.nanmax(PRES_mvp_corr_mat), length)
1408
1405
 
1409
- self.TEMP_ctd_on_mvp = mvp.vertical_interp(self.PRES_ctd, self.TEMP_ctd, pressure_grid)
1410
- self.PRES_ctd_on_mvp = mvp.vertical_interp(self.PRES_ctd, self.PRES_ctd, pressure_grid)
1411
- self.COND_ctd_on_mvp = mvp.vertical_interp(self.PRES_ctd, self.COND_ctd, pressure_grid)
1412
- self.SALT_ctd_on_mvp = mvp.vertical_interp(self.PRES_ctd, self.SALT_ctd, pressure_grid)
1413
- self.OXY_ctd_on_mvp = mvp.vertical_interp(self.PRES_ctd, self.OXY_ctd, pressure_grid)
1406
+ self.TEMP_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.TEMP_ctd, pressure_grid)
1407
+ self.PRES_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.PRES_ctd, pressure_grid)
1408
+ self.COND_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.COND_ctd, pressure_grid)
1409
+ self.SALT_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.SALT_ctd, pressure_grid)
1410
+ self.DO_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.OXY_ctd, pressure_grid)
1411
+ self.FLUO_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.FLUO_ctd, pressure_grid)
1412
+ self.TURB_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.TURB_ctd, pressure_grid)
1414
1413
  self.TEMP_mvp_corr_interp = mvp.vertical_interp(PRES_mvp_corr_mat, TEMP_mvp_corr_mat, pressure_grid)
1415
1414
  self.PRES_mvp_corr_interp = mvp.vertical_interp(PRES_mvp_corr_mat, PRES_mvp_corr_mat, pressure_grid)
1416
1415
  self.COND_mvp_corr_interp = mvp.vertical_interp(PRES_mvp_corr_mat, COND_mvp_corr_mat, pressure_grid)
1417
1416
  self.SALT_mvp_corr_interp = mvp.vertical_interp(PRES_mvp_corr_mat, SALT_mvp_corr_mat, pressure_grid)
1418
- self.SPEED_mvp_corr_interp = mvp.vertical_interp(PRES_mvp_corr_mat, SPEED_mvp_corr_mat, pressure_grid)
1417
+ self.DO_mvp_corr_interp = mvp.vertical_interp(self.PRES_mvp, self.DO_mvp, pressure_grid)
1418
+ self.FLUO_mvp_corr_interp = mvp.vertical_interp(self.PRES_mvp, self.FLUO_mvp, pressure_grid)
1419
+ self.TURB_mvp_corr_interp = mvp.vertical_interp(self.PRES_mvp, self.TURB_mvp, pressure_grid)
1420
+ self.PH_mvp_corr_interp = mvp.vertical_interp(self.PRES_mvp, self.PH_mvp, pressure_grid)
1421
+ self.SUNA_mvp_corr_interp = mvp.vertical_interp(self.PRES_mvp, self.SUNA_mvp, pressure_grid)
1422
+
1423
+ if self.speed:
1424
+ self.SPEED_mvp_corr_interp = mvp.vertical_interp(PRES_mvp_corr_mat, SPEED_mvp_corr_mat, pressure_grid)
1419
1425
  self.TIME_mvp_corr_interp = mvp.vertical_interp(PRES_mvp_corr_mat, TIME_mvp_corr_mat, pressure_grid)
1420
1426
 
1421
1427
  print('CTD data interpolated onto corrected MVP pressure levels.')
1422
1428
 
1423
1429
 
1424
- def to_netcdf(self, filepath=None, corrected=False, compression=True, engine=None, per_profile_files=False):
1430
+ def corrige_MVP_offset_on_ctd_exact(self,id_mvp,id_ctd,min_depth=-1):
1431
+ """
1432
+ This function corrects the offset between the MVP and CTD profiles by aligning the temperature, conductivity profiles. It calculates the mean difference in temperature between the two profiles and applies this correction to the CTD temperature data.
1433
+ id_mvp and id_ctd must be the same length as each MVP profile will be be corrected with the corresponding CTD profile. The function returns the corrected MVP temperature and conductivity profiles.
1434
+ This version of the correction suppose that CTD and MVP should be exactly the same profile (same location, same time). If it not the case, you shouldf use the other function _imple
1435
+ """
1436
+
1437
+ mean_temp_diff = []
1438
+ mean_cond_diff = []
1439
+
1440
+ print("Calculating mean differences between MVP and CTD profiles before correction:")
1441
+ for i in range(len(id_mvp)):
1442
+ # Calculate the mean difference in temperature between the MVP and CTD profiles
1443
+ temp_diff = np.nanmean(self.TEMP_mvp_corr_interp[id_mvp[i]] - self.TEMP_ctd_interp[id_ctd[i]])
1444
+ mean_temp_diff.append(temp_diff)
1445
+
1446
+ cond_diff = np.nanmean(self.COND_mvp_corr_interp[id_mvp[i]] - self.COND_ctd_interp[id_ctd[i]])
1447
+ mean_cond_diff.append(cond_diff)
1448
+ print("Mean temperature difference between MVP and CTD profiles:", np.mean(mean_temp_diff))
1449
+ print("Mean conductivity difference between MVP and CTD profiles:", np.mean(mean_cond_diff))
1450
+
1451
+ for i in range(len(id_mvp)):
1452
+ self.TEMP_mvp_corr_interp[id_mvp[i]] = mvp.align_profiles(self.PRES_mvp_corr_interp[id_mvp[i]], self.TEMP_ctd_interp[id_ctd[i]], self.TEMP_mvp_corr_interp[id_mvp[i]],min_depth)[0]
1453
+ self.COND_mvp_corr_interp[id_mvp[i]] = mvp.align_profiles(self.PRES_mvp_corr_interp[id_mvp[i]], self.COND_ctd_interp[id_ctd[i]], self.COND_mvp_corr_interp[id_mvp[i]],min_depth)[0]
1454
+
1455
+
1456
+ mean_temp_diff = []
1457
+ mean_cond_diff = []
1458
+ print("After correction:")
1459
+ for i in range(len(id_mvp)):
1460
+ # Calculate the mean difference in temperature between the MVP and CTD profiles
1461
+ temp_diff = np.nanmean(self.TEMP_mvp_corr_interp[id_mvp[i]] - self.TEMP_ctd_interp[id_ctd[i]])
1462
+ mean_temp_diff.append(temp_diff)
1463
+
1464
+ cond_diff = np.nanmean(self.COND_mvp_corr_interp[id_mvp[i]] - self.COND_ctd_interp[id_ctd[i]])
1465
+ mean_cond_diff.append(cond_diff)
1466
+ print("Mean temperature difference between MVP and CTD profiles:", np.mean(mean_temp_diff))
1467
+ print("Mean conductivity difference between MVP and CTD profiles:", np.mean(mean_cond_diff))
1468
+
1469
+
1470
+
1471
+ def corrige_MVP_offset_on_ctd_simple(self,id_mvp,id_ctd,min_depth):
1472
+ """
1473
+ This function corrects the offset between the MVP and CTD profiles by aligning the temperature, conductivity profiles. It calculates the mean difference in temperature between the two profiles and applies this correction to the CTD temperature data.
1474
+ id_mvp and id_ctd must be the same length as each MVP profile will be be corrected with the corresponding CTD profile. The function returns the corrected MVP temperature and conductivity profiles.
1475
+ This version of the correction is less restritive than the other one, does not need the CTD aand MVP profiles to be exactly similar
1476
+ We advice to choose a min_depth that avoid to take into acount the surface layer which can introduce errors.
1477
+ """
1478
+
1479
+ mean_temp_diff = []
1480
+ mean_cond_diff = []
1481
+ print("Calculating mean differences between MVP and CTD profiles before correction:")
1482
+ for i in range(len(id_mvp)):
1483
+ id_valid = self.PRES_mvp_corr_interp[id_mvp[i]] >= min_depth
1484
+ # Calculate the mean difference in temperature between the MVP and CTD profiles
1485
+ temp_diff = np.nanmean(self.TEMP_mvp_corr_interp[id_mvp[i], id_valid] - self.TEMP_ctd_interp[id_ctd[i], id_valid])
1486
+ mean_temp_diff.append(temp_diff)
1487
+
1488
+ cond_diff = np.nanmean(self.COND_mvp_corr_interp[id_mvp[i], id_valid] - self.COND_ctd_interp[id_ctd[i], id_valid])
1489
+ mean_cond_diff.append(cond_diff)
1490
+ print("Mean temperature difference between MVP and CTD profiles:", np.mean(mean_temp_diff))
1491
+ print("Mean conductivity difference between MVP and CTD profiles:", np.mean(mean_cond_diff))
1492
+
1493
+ for i in range(len(id_mvp)):
1494
+ id_valid = self.PRES_mvp_corr_interp[id_mvp[i]] >= min_depth
1495
+
1496
+ # Calculate the mean difference in temperature between the MVP and CTD profiles
1497
+ temp_diff = np.nanmean(self.TEMP_mvp_corr_interp[id_mvp[i], id_valid] - self.TEMP_ctd_interp[id_ctd[i], id_valid])
1498
+ self.TEMP_mvp_corr_interp[id_mvp[i]] -= temp_diff
1499
+
1500
+ cond_diff = np.nanmean(self.COND_mvp_corr_interp[id_mvp[i], id_valid] - self.COND_ctd_interp[id_ctd[i], id_valid])
1501
+ self.COND_mvp_corr_interp[id_mvp[i]] -= cond_diff
1502
+
1503
+
1504
+ mean_temp_diff = []
1505
+ mean_cond_diff = []
1506
+ print("After correction:")
1507
+ for i in range(len(id_mvp)):
1508
+ id_valid = self.PRES_mvp_corr_interp[id_mvp[i]] >= min_depth
1509
+
1510
+ # Calculate the mean difference in temperature between the MVP and CTD profiles
1511
+ temp_diff = np.nanmean(self.TEMP_mvp_corr_interp[id_mvp[i], id_valid] - self.TEMP_ctd_interp[id_ctd[i], id_valid])
1512
+ mean_temp_diff.append(temp_diff)
1513
+
1514
+ cond_diff = np.nanmean(self.COND_mvp_corr_interp[id_mvp[i], id_valid] - self.COND_ctd_interp[id_ctd[i], id_valid])
1515
+ mean_cond_diff.append(cond_diff)
1516
+ print("Mean temperature difference between MVP and CTD profiles:", np.mean(mean_temp_diff))
1517
+ print("Mean conductivity difference between MVP and CTD profiles:", np.mean(mean_cond_diff))
1518
+
1519
+
1520
+
1521
+
1522
+ def to_netcdf(self, filepath, corrected=False, compression=True, engine=None, per_profile_files=False):
1425
1523
  """
1426
1524
  Export MVP data to a NetCDF file using xarray.
1427
1525
 
@@ -1591,7 +1689,7 @@ class Analyzer:
1591
1689
  'title': 'MVP profile data',
1592
1690
  'Conventions': 'CF-1.8',
1593
1691
  'institution': 'LMD/CNRS',
1594
- 'source': 'MVPAnalyzer',
1692
+ 'source': 'PyMVP',
1595
1693
  'history': f"Created on {datetime.now().isoformat()}",
1596
1694
  'mvp_Yorig': int(self.Yorig)
1597
1695
  }
@@ -1606,17 +1704,13 @@ class Analyzer:
1606
1704
  elif engine == 'h5netcdf':
1607
1705
  encoding = {name: {'compression': 'gzip', 'compression_opts': 4} for name in data_vars.keys()}
1608
1706
 
1609
- # Determine output base directory
1610
- if filepath is None:
1611
- base_dir = self.output_path if hasattr(self, 'output_path') else os.getcwd() + os.sep
1612
- else:
1613
- # If a full file path was provided and not per_profile_files, honor it
1614
- if (not per_profile_files) and filepath.lower().endswith('.nc'):
1615
- out_path = filepath
1616
- ds.to_netcdf(out_path, encoding=encoding, engine=engine)
1617
- print(f"NetCDF written: {out_path} using engine={engine}")
1618
- return
1619
- base_dir = filepath
1707
+
1708
+ if (not per_profile_files) and filepath.lower().endswith('.nc'):
1709
+ out_path = filepath
1710
+ ds.to_netcdf(out_path, encoding=encoding, engine=engine)
1711
+ print(f"NetCDF written: {out_path} using engine={engine}")
1712
+ return
1713
+ base_dir = filepath
1620
1714
 
1621
1715
  if not base_dir.endswith(os.sep):
1622
1716
  base_dir = base_dir + os.sep
@@ -36,12 +36,11 @@ import scipy.stats as st
36
36
  from datetime import date
37
37
  from datetime import datetime
38
38
  from scipy import interpolate
39
- from scipy.signal import butter, freqz
40
39
  from scipy import signal
41
40
  import gsw
42
- from scipy.interpolate import pchip_interpolate
41
+ from scipy.interpolate import interp1d
43
42
  from netCDF4 import Dataset
44
- from scipy.signal import butter, filtfilt, correlate, correlation_lags
43
+ from scipy.signal import butter, filtfilt, correlate, correlation_lags,savgol_filter
45
44
 
46
45
  #
47
46
  ################################################################################
@@ -993,3 +992,85 @@ def bin_average_v2(P,T,C,S,time,dp=0.05):
993
992
  np.array(C_bin),
994
993
  np.array(S_bin),
995
994
  np.array(time_bin))
995
+
996
+
997
+
998
+
999
+
1000
+
1001
+
1002
+ def align_profiles(P, T_ref, T_to_align_raw, min_depth=0,max_shift=20):
1003
+ """
1004
+ Pipeline complet :
1005
+ - estime ΔP
1006
+ - recale
1007
+ - estime ΔT
1008
+ - corrige
1009
+ """
1010
+
1011
+ ### 1. calcul delta de pression
1012
+
1013
+ # Masque pour exclure les valeurs non finies
1014
+ mask_nan = (
1015
+ np.isfinite(P) &
1016
+ np.isfinite(T_ref) &
1017
+ np.isfinite(T_to_align_raw)
1018
+ )
1019
+
1020
+ P = P[mask_nan]
1021
+ T_ref = T_ref[mask_nan]
1022
+ T_to_align = T_to_align_raw[mask_nan]
1023
+
1024
+ # Masque pour exclure la surface
1025
+ mask = P >= min_depth
1026
+
1027
+ P = P[mask]
1028
+ T_ref = T_ref[mask]
1029
+ T_to_align = T_to_align[mask]
1030
+
1031
+ # Lissage léger
1032
+ T1s = savgol_filter(T_ref, 11, 2)
1033
+ T2s = savgol_filter(T_to_align, 11, 2)
1034
+
1035
+ # Gradients
1036
+ dT1 = np.gradient(T1s, P)
1037
+ dT2 = np.gradient(T2s, P)
1038
+
1039
+ # Normalisation (important pour corrélation)
1040
+ dT1 = (dT1 - np.mean(dT1)) / np.std(dT1)
1041
+ dT2 = (dT2 - np.mean(dT2)) / np.std(dT2)
1042
+
1043
+ # Corrélation
1044
+ corr = correlate(dT2, dT1, mode='full')
1045
+ lags = np.arange(-len(dT1)+1, len(dT1))
1046
+
1047
+ # Convertir en décalage en pression
1048
+ dP = np.mean(np.diff(P))
1049
+ shifts = lags * dP
1050
+
1051
+ # Limiter les shifts plausibles
1052
+ valid = np.abs(shifts) <= max_shift
1053
+
1054
+ deltaP = shifts[valid][np.argmax(corr[valid])]
1055
+
1056
+ ### 2. recalage pression
1057
+ f = interp1d(P + deltaP, T_to_align, bounds_error=False, fill_value=np.nan)
1058
+ T_shifted = f(P)
1059
+
1060
+ ### 3. calcul delta de température
1061
+ mask = (P >= min_depth) & np.isfinite(T_ref) & np.isfinite(T_shifted)
1062
+ deltaT = np.median(T_shifted[mask] - T_ref[mask])
1063
+
1064
+ ### 4. recalage thermique
1065
+ T_corrected = T_shifted - deltaT
1066
+
1067
+ mask_corrected = np.isfinite(T_corrected)
1068
+
1069
+ # copie pour ne pas modifier l'original directement
1070
+ T_out = T_to_align_raw.copy()
1071
+
1072
+ # injection uniquement là où c’est valide
1073
+ T_out_indices = np.where(mask_nan)[0]
1074
+ T_out[T_out_indices[mask_corrected]] = T_corrected[mask_corrected]
1075
+
1076
+ return T_out, deltaP, deltaT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyMVP
3
- Version: 0.1.9
3
+ Version: 0.2.1
4
4
  Summary: Python package for Moving Vessel Profiler correction and analysis
5
5
  Author: MaximilienWemaere
6
6
  Requires-Python: >=3.10
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "PyMVP"
3
- version = "0.1.9"
3
+ version = "0.2.1"
4
4
  description = "Python package for Moving Vessel Profiler correction and analysis"
5
5
  authors = [{name="MaximilienWemaere"}]
6
6
  readme = "README.md"
File without changes
File without changes
File without changes
File without changes