PyMVP 0.1.8__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.8
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
 
@@ -629,14 +625,8 @@ class Analyzer:
629
625
  print('CTD data loaded successfully.')
630
626
  self.ctd = True
631
627
 
632
- return
633
628
 
634
629
 
635
-
636
-
637
-
638
-
639
-
640
630
  def compute_waterflow(self,horizontal_speed=2,corr=False):
641
631
  """
642
632
  Compute the water flow speed (u,v) from the horizontal speed and the direction of the profiles.
@@ -656,6 +646,7 @@ class Analyzer:
656
646
 
657
647
  self.SPEED_mvp = SPEED_MVP
658
648
  print('Water flow speed computed successfully.')
649
+ self.speed = True
659
650
 
660
651
  def print_profile_metadata(self):
661
652
  """
@@ -939,7 +930,7 @@ class Analyzer:
939
930
  plt.xlabel('Fluorescence, ug/L')
940
931
  plt.ylabel('Pressure, dbar')
941
932
 
942
- 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):
943
934
  """
944
935
  Plot the TS diagram (Salinity vs Temperature) for one or more profiles, with isopycnals.
945
936
  Args:
@@ -1235,7 +1226,7 @@ class Analyzer:
1235
1226
  print(f" MVP down: {rmse_cond_down:.4f} S/m (deep: {rmse_cond_down_deep:.4f} S/m)")
1236
1227
  print(f" MVP up: {rmse_cond_up:.4f} S/m (deep: {rmse_cond_up_deep:.4f} S/m)")
1237
1228
 
1238
- 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,):
1239
1230
  """
1240
1231
  Apply oxygen correction to MVP dissolved oxygen profiles thanks to CTD data.
1241
1232
  Args:
@@ -1296,9 +1287,9 @@ class Analyzer:
1296
1287
  rmse_after_full = np.mean(np.sqrt(np.nanmean((DO_mvp_corr_full_interp - DO_ctd_interp)**2,axis=1)))
1297
1288
  print(f"RMSE after correction (full profile): {rmse_after_full:.4f}")
1298
1289
 
1290
+ self.DO_mvp_raw = self.DO_mvp.copy()
1291
+ self.DO_mvp = DO_mvp_corr_full
1299
1292
 
1300
- if correction:
1301
- self.DO_mvp = DO_mvp_corr_full
1302
1293
 
1303
1294
  if plotting:
1304
1295
 
@@ -1315,7 +1306,6 @@ class Analyzer:
1315
1306
  plt.show()
1316
1307
 
1317
1308
 
1318
-
1319
1309
  def mvp_correction(self,high_cutoff=1,dp=0.1):
1320
1310
 
1321
1311
  T_MVP_corr = []
@@ -1369,7 +1359,7 @@ class Analyzer:
1369
1359
  self.COND_mvp_corr = {i: sublist for i, sublist in enumerate(C_MVP_corr)}
1370
1360
  self.SALT_mvp_corr = {i: sublist for i, sublist in enumerate(S_MVP_corr)}
1371
1361
  self.TIME_mvp_corr = {i: sublist for i, sublist in enumerate(Time_MVP_corr)}
1372
-
1362
+ self.corrected = True
1373
1363
 
1374
1364
  print("MVP profiles corrected.")
1375
1365
 
@@ -1381,6 +1371,11 @@ class Analyzer:
1381
1371
  """
1382
1372
  if not self.ctd:
1383
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
+
1384
1379
 
1385
1380
  if not hasattr(self, 'PRES_mvp_corr'):
1386
1381
  raise ValueError("Corrected MVP data not available. Apply corrections first.")
@@ -1398,8 +1393,9 @@ class Analyzer:
1398
1393
  max_lensalt = max([len(p) for p in self.SALT_mvp_corr.values()])
1399
1394
  SALT_mvp_corr_mat = np.array([list(row) + [np.nan] * (max_lensalt - len(row)) for row in self.SALT_mvp_corr.values()])
1400
1395
 
1401
- max_lenvspd = max([len(p) for p in self.SPEED_mvp_corr.values()])
1402
- 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()])
1403
1399
 
1404
1400
  max_lentime = max([len(p) for p in self.TIME_mvp_corr.values()])
1405
1401
  TIME_mvp_corr_mat = np.array([list(row) + [np.nan] * (max_lentime - len(row)) for row in self.TIME_mvp_corr.values()])
@@ -1407,22 +1403,123 @@ class Analyzer:
1407
1403
 
1408
1404
  pressure_grid = np.linspace(np.nanmin(PRES_mvp_corr_mat), np.nanmax(PRES_mvp_corr_mat), length)
1409
1405
 
1410
- self.TEMP_ctd_on_mvp = mvp.vertical_interp(self.PRES_ctd, self.TEMP_ctd, pressure_grid)
1411
- self.PRES_ctd_on_mvp = mvp.vertical_interp(self.PRES_ctd, self.PRES_ctd, pressure_grid)
1412
- self.COND_ctd_on_mvp = mvp.vertical_interp(self.PRES_ctd, self.COND_ctd, pressure_grid)
1413
- self.SALT_ctd_on_mvp = mvp.vertical_interp(self.PRES_ctd, self.SALT_ctd, pressure_grid)
1414
- 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)
1415
1413
  self.TEMP_mvp_corr_interp = mvp.vertical_interp(PRES_mvp_corr_mat, TEMP_mvp_corr_mat, pressure_grid)
1416
1414
  self.PRES_mvp_corr_interp = mvp.vertical_interp(PRES_mvp_corr_mat, PRES_mvp_corr_mat, pressure_grid)
1417
1415
  self.COND_mvp_corr_interp = mvp.vertical_interp(PRES_mvp_corr_mat, COND_mvp_corr_mat, pressure_grid)
1418
1416
  self.SALT_mvp_corr_interp = mvp.vertical_interp(PRES_mvp_corr_mat, SALT_mvp_corr_mat, pressure_grid)
1419
- 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)
1420
1425
  self.TIME_mvp_corr_interp = mvp.vertical_interp(PRES_mvp_corr_mat, TIME_mvp_corr_mat, pressure_grid)
1421
1426
 
1422
1427
  print('CTD data interpolated onto corrected MVP pressure levels.')
1423
1428
 
1424
1429
 
1425
- 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):
1426
1523
  """
1427
1524
  Export MVP data to a NetCDF file using xarray.
1428
1525
 
@@ -1592,7 +1689,7 @@ class Analyzer:
1592
1689
  'title': 'MVP profile data',
1593
1690
  'Conventions': 'CF-1.8',
1594
1691
  'institution': 'LMD/CNRS',
1595
- 'source': 'MVPAnalyzer',
1692
+ 'source': 'PyMVP',
1596
1693
  'history': f"Created on {datetime.now().isoformat()}",
1597
1694
  'mvp_Yorig': int(self.Yorig)
1598
1695
  }
@@ -1607,17 +1704,13 @@ class Analyzer:
1607
1704
  elif engine == 'h5netcdf':
1608
1705
  encoding = {name: {'compression': 'gzip', 'compression_opts': 4} for name in data_vars.keys()}
1609
1706
 
1610
- # Determine output base directory
1611
- if filepath is None:
1612
- base_dir = self.output_path if hasattr(self, 'output_path') else os.getcwd() + os.sep
1613
- else:
1614
- # If a full file path was provided and not per_profile_files, honor it
1615
- if (not per_profile_files) and filepath.lower().endswith('.nc'):
1616
- out_path = filepath
1617
- ds.to_netcdf(out_path, encoding=encoding, engine=engine)
1618
- print(f"NetCDF written: {out_path} using engine={engine}")
1619
- return
1620
- 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
1621
1714
 
1622
1715
  if not base_dir.endswith(os.sep):
1623
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.8
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.8"
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