PyMVP 0.2.3__tar.gz → 0.2.5__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.2.3
3
+ Version: 0.2.5
4
4
  Summary: Python package for Moving Vessel Profiler correction and analysis
5
5
  Author: MaximilienWemaere
6
6
  Requires-Python: >=3.10
@@ -114,8 +114,8 @@ class Analyzer:
114
114
  self.PH_mvp = nc['PH'].values
115
115
  self.SALT_mvp = nc['SAL'].values
116
116
  self.TIME_mvp = nc['TIME_s'].values
117
- self.LAT_mvp = nc['LATITUDE'].values
118
- self.LON_mvp = nc['LONGITUDE'].values
117
+ self.Lat_mvp = nc['LATITUDE'].values
118
+ self.Lon_mvp = nc['LONGITUDE'].values
119
119
  self.DATETIME_mvp = nc['profile_time'].values
120
120
  self.DIR = nc['direction'].values
121
121
  self.label_mvp = nc['profile'].values
@@ -293,8 +293,8 @@ class Analyzer:
293
293
  self.PH_mvp = PH_mvp
294
294
  self.SALT_mvp = SALT_mvp
295
295
  self.TIME_mvp = TIME_mvp
296
- self.LAT_mvp = LAT_mvp
297
- self.LON_mvp = LON_mvp
296
+ self.Lat_mvp = LAT_mvp
297
+ self.Lon_mvp = LON_mvp
298
298
  self.DATETIME_mvp = DATETIME_mvp
299
299
  self.DIR = DIR
300
300
  self.label_mvp = Label_mvp
@@ -341,8 +341,8 @@ class Analyzer:
341
341
  self.PH_mvp = nc['PH'].values
342
342
  self.SALT_mvp = nc['SAL'].values
343
343
  self.TIME_mvp = nc['TIME'].values
344
- self.LAT_mvp = nc['LATITUDE'].values
345
- self.LON_mvp = nc['LONGITUDE'].values
344
+ self.Lat_mvp = nc['LATITUDE'].values
345
+ self.Lon_mvp = nc['LONGITUDE'].values
346
346
  self.DATETIME_mvp = nc['profile_time'].values
347
347
  self.DIR = nc['direction'].values
348
348
  self.Label_mvp = nc['profile'].values
@@ -481,8 +481,8 @@ class Analyzer:
481
481
  self.PH_mvp = np.hstack((self.PH_mvp, nan_cols))
482
482
  self.SALT_mvp = np.hstack((self.SALT_mvp, nan_cols))
483
483
  self.TIME_mvp = np.hstack((self.TIME_mvp, nan_cols))
484
- self.LAT_mvp = np.hstack((self.LAT_mvp, nan_cols))
485
- self.LON_mvp = np.hstack((self.LON_mvp, nan_cols))
484
+ self.Lat_mvp = np.hstack((self.Lat_mvp, nan_cols))
485
+ self.Lon_mvp = np.hstack((self.Lon_mvp, nan_cols))
486
486
 
487
487
 
488
488
 
@@ -547,8 +547,8 @@ class Analyzer:
547
547
  self.PH_mvp = np.concatenate((self.PH_mvp, PH_mvp), axis=0)
548
548
  self.SALT_mvp = np.concatenate((self.SALT_mvp, SALT_mvp), axis=0)
549
549
  self.TIME_mvp = np.concatenate((self.TIME_mvp, TIME_mvp), axis=0)
550
- self.LAT_mvp = np.concatenate((self.LAT_mvp, LAT_mvp), axis=0)
551
- self.LON_mvp = np.concatenate((self.LON_mvp, LON_mvp), axis=0)
550
+ self.Lat_mvp = np.concatenate((self.Lat_mvp, LAT_mvp), axis=0)
551
+ self.Lon_mvp = np.concatenate((self.Lon_mvp, LON_mvp), axis=0)
552
552
 
553
553
  self.DATETIME_mvp.extend(DATETIME_mvp)
554
554
  self.DIR.extend(DIR)
@@ -585,7 +585,7 @@ class Analyzer:
585
585
  TEMP_ctd_temp = []
586
586
  COND_ctd_temp = []
587
587
  TURB_ctd_temp = []
588
- OXY_ctd_temp = []
588
+ DO_ctd_temp = []
589
589
  FLUO_ctd_temp = []
590
590
  CDOM_ctd_temp = []
591
591
  DATETIME_ctd = []
@@ -603,8 +603,8 @@ class Analyzer:
603
603
  SALT_ctd_temp.append(nc['SAL'].values[1])
604
604
  TURB_ctd_temp.append(nc['TURB'].values[0])
605
605
  TURB_ctd_temp.append(nc['TURB'].values[1])
606
- OXY_ctd_temp.append(nc['OXY'].values[0])
607
- OXY_ctd_temp.append(nc['OXY'].values[1])
606
+ DO_ctd_temp.append(nc['OXY'].values[0])
607
+ DO_ctd_temp.append(nc['OXY'].values[1])
608
608
  FLUO_ctd_temp.append(nc['FLUO'].values[0])
609
609
  FLUO_ctd_temp.append(nc['FLUO'].values[1])
610
610
  CDOM_ctd_temp.append(nc['CDOM'].values[0])
@@ -622,12 +622,13 @@ class Analyzer:
622
622
  self.COND_ctd = np.array(COND_ctd_temp)
623
623
  self.SALT_ctd = np.array(SALT_ctd_temp)
624
624
  self.TURB_ctd = np.array(TURB_ctd_temp)
625
- self.OXY_ctd = np.array(OXY_ctd_temp)
625
+ self.DO_ctd = np.array(DO_ctd_temp)
626
626
  self.FLUO_ctd = np.array(FLUO_ctd_temp)
627
627
  self.CDOM_ctd = np.array(CDOM_ctd_temp)
628
628
  self.LAT_ctd = np.array(LAT_ctd_temp)
629
629
  self.LON_ctd = np.array(LON_ctd_temp)
630
630
  self.DATETIME_ctd = np.array(DATETIME_ctd)
631
+ self.TIME_ctd = np.array([(np.datetime64(dt) - np.datetime64(self.date_ref)) / np.timedelta64(1, 'D') for dt in self.DATETIME_ctd])
631
632
 
632
633
 
633
634
  print('CTD data loaded successfully.')
@@ -687,7 +688,7 @@ class Analyzer:
687
688
  print('MVP data:')
688
689
  print('Number of profiles: ' + str(len(self.DATETIME_mvp)))
689
690
  for i in range(0,len(self.DATETIME_mvp)):
690
- print(f" Profil down {2*i} - Profil up {2*i+1} - Latitude: {self.LAT_mvp[2*i,0]:.5f}, Longitude: {self.LON_mvp[2*i,0]:.5f}, Date/Heure: {self.DATETIME_mvp[i]}")
691
+ print(f" Profil down {2*i} - Profil up {2*i+1} - Latitude: {self.Lat_mvp[2*i,0]:.5f}, Longitude: {self.Lon_mvp[2*i,0]:.5f}, Date/Heure: {self.DATETIME_mvp[i]}")
691
692
 
692
693
  if self.ctd:
693
694
  print('CTD data:')
@@ -731,8 +732,8 @@ class Analyzer:
731
732
  self.PH_mvp = self.PH_mvp[l_id,:]
732
733
  self.SALT_mvp = self.SALT_mvp[l_id,:]
733
734
  self.TIME_mvp = self.TIME_mvp[l_id,:]
734
- self.LAT_mvp = self.LAT_mvp[l_id,:]
735
- self.LON_mvp = self.LON_mvp[l_id,:]
735
+ self.Lat_mvp = self.Lat_mvp[l_id,:]
736
+ self.Lon_mvp = self.Lon_mvp[l_id,:]
736
737
  self.DATETIME_mvp = np.array(self.DATETIME_mvp)[l_id2]
737
738
  self.DIR = np.array(self.DIR)[l_id]
738
739
  self.label_mvp = np.array(self.label_mvp)[l_id]
@@ -751,7 +752,7 @@ class Analyzer:
751
752
  self.SALT_ctd = self.SALT_ctd[l_id,:]
752
753
  self.COND_ctd = self.COND_ctd[l_id,:]
753
754
  self.TURB_ctd = self.TURB_ctd[l_id,:]
754
- self.OXY_ctd = self.OXY_ctd[l_id,:]
755
+ self.DO_ctd = self.DO_ctd[l_id,:]
755
756
  self.FLUO_ctd = self.FLUO_ctd[l_id,:]
756
757
  self.CDOM_ctd = self.CDOM_ctd[l_id,:]
757
758
  self.LAT_ctd = self.LAT_ctd[l_id,:]
@@ -834,7 +835,7 @@ class Analyzer:
834
835
 
835
836
  put_label = True
836
837
  c = 0
837
- for i in range(0,self.LAT_mvp.shape[0],2):
838
+ for i in range(0,self.Lat_mvp.shape[0],2):
838
839
  if i>0:
839
840
  if self.label_mvp[i] == self.label_mvp[i-1]:
840
841
  put_label = False
@@ -842,8 +843,8 @@ class Analyzer:
842
843
  put_label = True
843
844
  c+=1
844
845
 
845
- lat = self.LAT_mvp[i,0] if self.LAT_mvp.ndim == 2 else self.LAT_mvp[i]
846
- lon = self.LON_mvp[i,0] if self.LON_mvp.ndim == 2 else self.LON_mvp[i]
846
+ lat = self.Lat_mvp[i,0] if self.Lat_mvp.ndim == 2 else self.Lat_mvp[i]
847
+ lon = self.Lon_mvp[i,0] if self.Lon_mvp.ndim == 2 else self.Lon_mvp[i]
847
848
  ax.scatter(lon, lat, color=colors[c], marker='o', label='MVP '+self.label_mvp[i] if put_label else "", transform=ccrs.PlateCarree())
848
849
 
849
850
  # CTD
@@ -925,8 +926,8 @@ class Analyzer:
925
926
  plt.plot(self.DO_mvp[id_mvp],self.PRES_mvp[id_mvp],label='MVP down')
926
927
  plt.plot(self.DO_mvp[id_mvp+1],self.PRES_mvp[id_mvp+1],label='MVP up')
927
928
  if self.ctd:
928
- plt.plot(self.OXY_ctd[id_ctd],self.PRES_ctd[id_ctd],label='CTD down')
929
- plt.plot(self.OXY_ctd[id_ctd+1],self.PRES_ctd[id_ctd+1],label='CTD up')
929
+ plt.plot(self.DO_ctd[id_ctd],self.PRES_ctd[id_ctd],label='CTD down')
930
+ plt.plot(self.DO_ctd[id_ctd+1],self.PRES_ctd[id_ctd+1],label='CTD up')
930
931
  plt.legend()
931
932
  plt.gca().invert_yaxis()
932
933
  plt.grid()
@@ -1073,7 +1074,7 @@ class Analyzer:
1073
1074
 
1074
1075
  TEMP_ctd_interp = mvp.vertical_interp(self.PRES_ctd[id_ctd1,:],self.TEMP_ctd[id_ctd1,:], pressure_grid)
1075
1076
  SALT_ctd_interp = mvp.vertical_interp(self.PRES_ctd[id_ctd1,:],self.SALT_ctd[id_ctd1,:], pressure_grid)
1076
- DO_ctd_interp = mvp.vertical_interp(self.PRES_ctd[id_ctd1,:],self.OXY_ctd[id_ctd1,:], pressure_grid)
1077
+ DO_ctd_interp = mvp.vertical_interp(self.PRES_ctd[id_ctd1,:],self.DO_ctd[id_ctd1,:], pressure_grid)
1077
1078
  COND_ctd_interp = mvp.vertical_interp(self.PRES_ctd[id_ctd1,:],self.COND_ctd[id_ctd1,:], pressure_grid)
1078
1079
 
1079
1080
  # differences study between MVP down and CTD profiles
@@ -1256,7 +1257,7 @@ class Analyzer:
1256
1257
  print(f" MVP down: {rmse_cond_down:.4f} S/m (deep: {rmse_cond_down_deep:.4f} S/m)")
1257
1258
  print(f" MVP up: {rmse_cond_up:.4f} S/m (deep: {rmse_cond_up_deep:.4f} S/m)")
1258
1259
 
1259
- def correct_oxygen(self,id_mvp=None,id_ctd=None,num_sample=500,plotting=False,):
1260
+ def correct_oxygen(self,id_mvp=None,id_ctd=None,plotting=False,):
1260
1261
  """
1261
1262
  Apply oxygen correction to MVP dissolved oxygen profiles thanks to CTD data.
1262
1263
  Args:
@@ -1278,63 +1279,64 @@ class Analyzer:
1278
1279
  id_ctd = id_mvp
1279
1280
 
1280
1281
 
1281
- # Interpolate MVP and CTD data to match pressure levels
1282
- pmin = np.nanmin(self.PRES_mvp)
1283
- pmax = np.nanmax(self.PRES_mvp)
1284
- pressure_grid = np.linspace(pmin, pmax, num_sample)
1285
-
1282
+ if hasattr(self,'DO_mvp_corr_interp') == False:
1283
+ raise ValueError("Please run the interpolation method first to create the DO_mvp_corr_interp attribute.")
1284
+
1286
1285
 
1287
- DO_mvp_interp = mvp.vertical_interp(self.PRES_mvp[id_mvp,:], self.DO_mvp[id_mvp,:], pressure_grid)
1288
- DO_ctd_interp = mvp.vertical_interp(self.PRES_ctd[id_ctd,:],self.OXY_ctd[id_ctd,:], pressure_grid)
1286
+ oxy_mvp = self.DO_mvp_corr_interp[id_mvp]
1287
+ oxy_ctd = self.DO_ctd_interp[id_ctd]
1288
+ pres = self.PRES_mvp_corr_interp[id_mvp]
1289
1289
 
1290
- mask = ~np.isnan(DO_mvp_interp) & ~np.isnan(DO_ctd_interp)
1291
- pressure_grid = pressure_grid[mask[0]]
1292
- DO_mvp_interp = DO_mvp_interp[mask]
1293
- DO_ctd_interp = DO_ctd_interp[mask]
1290
+ mask = ~np.isnan(oxy_mvp) & ~np.isnan(oxy_ctd)
1291
+ oxy_mvp = oxy_mvp[mask]
1292
+ oxy_ctd = oxy_ctd[mask]
1293
+ pres = pres[mask]
1294
1294
 
1295
- diff = DO_mvp_interp-DO_ctd_interp
1295
+ diff = oxy_mvp - oxy_ctd
1296
1296
 
1297
- A = np.vstack([pressure_grid, np.ones_like(pressure_grid)]).T
1298
- print(A.shape, diff.shape)
1297
+ A = np.vstack([oxy_ctd, np.ones(len(oxy_ctd))]).T
1299
1298
  diff = diff.flatten()
1300
- a_estime, b_estime = np.linalg.lstsq(A, diff, rcond=None)[0]
1301
-
1302
- print(f"Pente estimée (a) : {a_estime:.6f} ")
1303
- print(f"Biais estimé (b) : {b_estime:.6f} ")
1304
-
1305
- DO_mvp_corr = DO_mvp_interp - (a_estime*pressure_grid + b_estime)
1306
-
1307
-
1308
1299
 
1309
- rmse_before = np.sqrt(np.nanmean((DO_mvp_interp - DO_ctd_interp)**2))
1310
- rmse_after = np.sqrt(np.nanmean((DO_mvp_corr - DO_ctd_interp)**2))
1311
- print(f"RMSE before correction: {rmse_before:.4f}")
1312
- print(f"RMSE after correction: {rmse_after:.4f}")
1300
+ a_estim, b_estim = np.linalg.lstsq(A, diff, rcond=None)[0]
1301
+ print(f"Estimated linear relationship: diff = {a_estim:.4f} * oxy_ctd + {b_estim:.4f}")
1313
1302
 
1314
- DO_mvp_corr_full = self.DO_mvp - (a_estime*self.PRES_mvp + b_estime)
1303
+ Do_mvp_corr = self.DO_mvp_corr_interp[id_mvp] - (a_estim * self.DO_ctd_interp[id_ctd] + b_estim)
1315
1304
 
1316
- DO_mvp_corr_full_interp = mvp.vertical_interp(self.PRES_mvp, DO_mvp_corr_full, pressure_grid)
1317
- rmse_after_full = np.mean(np.sqrt(np.nanmean((DO_mvp_corr_full_interp - DO_ctd_interp)**2,axis=1)))
1318
- print(f"RMSE after correction (full profile): {rmse_after_full:.4f}")
1319
1305
 
1320
- self.DO_mvp_raw = self.DO_mvp.copy()
1321
- self.DO_mvp = DO_mvp_corr_full
1306
+ rmse_before = np.sqrt(np.nanmean((self.DO_mvp_corr_interp[id_mvp] - self.DO_ctd_interp[id_ctd])**2))
1307
+ rmse_after = np.sqrt(np.nanmean((Do_mvp_corr - self.DO_ctd_interp[id_ctd])**2))
1308
+ if plotting:
1309
+ print(f"RMSE before correction: {rmse_before:.4f}")
1310
+ print(f"RMSE after correction: {rmse_after:.4f}")
1322
1311
 
1323
1312
 
1324
1313
  if plotting:
1325
1314
 
1326
- plt.figure()
1327
- plt.plot(DO_mvp_interp,pressure_grid,label='MVP')
1328
- plt.plot(DO_ctd_interp,pressure_grid,label='CTD')
1329
- plt.plot(DO_mvp_corr,pressure_grid,label='MVP corrected')
1315
+ plt.figure(figsize=(6,8))
1316
+ plt.plot(oxy_mvp, pres, label='MVP DO')
1317
+ plt.plot(Do_mvp_corr[mask],pres,label='MVP DO corrigé')
1318
+ plt.plot(oxy_ctd, pres, label='CTD DO')
1330
1319
  plt.gca().invert_yaxis()
1331
- plt.xlabel('Dissolved Oxygen, %')
1332
- plt.ylabel('Pressure, dbar')
1333
- plt.title('Oxygen correction')
1320
+ plt.xlabel('Oxygène dissous [µmol/kg]')
1321
+ plt.ylabel('Profondeur [m]')
1322
+ plt.title(f'Profil de DO - Profil {id_mvp} MVP vs Profil {id_ctd} CTD')
1334
1323
  plt.legend()
1335
- plt.grid()
1336
1324
  plt.show()
1337
1325
 
1326
+ self.DO_mvp_corr_interp[id_mvp] = Do_mvp_corr
1327
+
1328
+
1329
+ def correct_oxygen_all(self,mode):
1330
+
1331
+
1332
+
1333
+ for id_mvp in range(0,self.PRES_mvp.shape[0]):
1334
+
1335
+ id_nearest_ctd = mvp.find_nearest_profile(self.TIME_mvp_corr_interp[id_mvp],self.Lat_mvp_corr_interp[id_mvp], self.Lon_mvp_corr_interp[id_mvp],self.TIME_ctd ,self.LAT_ctd, self.LON_ctd,mode)[0]
1336
+ self.correct_oxygen(id_mvp=id_mvp, id_ctd=id_nearest_ctd, plotting=False)
1337
+
1338
+ print("Oxygen correction applied to all MVP profiles using nearest CTD profiles.")
1339
+
1338
1340
 
1339
1341
  def mvp_correction(self,high_cutoff=1,dp=0.1):
1340
1342
 
@@ -1437,7 +1439,7 @@ class Analyzer:
1437
1439
  self.PRES_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.PRES_ctd, pressure_grid)
1438
1440
  self.COND_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.COND_ctd, pressure_grid)
1439
1441
  self.SALT_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.SALT_ctd, pressure_grid)
1440
- self.DO_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.OXY_ctd, pressure_grid)
1442
+ self.DO_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.DO_ctd, pressure_grid)
1441
1443
  self.FLUO_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.FLUO_ctd, pressure_grid)
1442
1444
  self.TURB_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.TURB_ctd, pressure_grid)
1443
1445
  self.TEMP_mvp_corr_interp = mvp.vertical_interp(PRES_mvp_corr_mat, TEMP_mvp_corr_mat, pressure_grid)
@@ -1500,7 +1502,6 @@ class Analyzer:
1500
1502
  print("Mean conductivity difference between MVP and CTD profiles:", np.mean(mean_cond_diff))
1501
1503
 
1502
1504
 
1503
-
1504
1505
  def corrige_MVP_offset_on_ctd_simple(self,id_mvp,id_ctd,min_depth):
1505
1506
  """
1506
1507
  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.
@@ -1550,235 +1551,241 @@ class Analyzer:
1550
1551
  print("Mean conductivity difference between MVP and CTD profiles:", np.mean(mean_cond_diff))
1551
1552
 
1552
1553
 
1554
+ def corrige_MVP_offset_on_ctd_all(self,min_depth,mode):
1553
1555
 
1556
+ for id_mvp in range(self.PRES_mvp.shape[0]):
1557
+ id_nearest_ctd = mvp.find_nearest_profile(self.TIME_mvp_corr_interp[id_mvp],self.Lat_mvp_corr_interp[id_mvp], self.Lon_mvp_corr_interp[id_mvp],self.TIME_ctd ,self.LAT_ctd, self.LON_ctd,mode)[0]
1558
+ self.corrige_MVP_offset_on_ctd_simple(id_mvp=id_mvp, id_ctd=id_nearest_ctd, min_depth=min_depth)
1554
1559
 
1555
- def to_netcdf(self, filepath, corrected=False, compression=True, engine=None, per_profile_files=False):
1556
- """
1557
- Export MVP data to a NetCDF file using xarray.
1560
+ print("MVP profiles corrected for offset against CTD profiles using nearest CTD profiles.")
1558
1561
 
1559
- Args:
1560
- filepath (str): Output NetCDF file path.
1561
- corrected (bool): Also write corrected arrays if present (*_mvp_corr). Default False.
1562
- compression (bool): Enable compression (engine dependent). Default True.
1563
- engine (str|None): One of 'netcdf4', 'h5netcdf', 'scipy'. If None, choose netcdf4.
1564
- per_profile_files (bool): If True, write one .nc per MVP cycle (two rows: down and up).
1565
- """
1566
- if not getattr(self, 'mvp', False):
1567
- raise RuntimeError("No MVP data loaded. Call load_mvp_data() first.")
1562
+ def to_netcdf(self, filepath, corrected=False, compression=True, engine=None, per_profile_files=False):
1563
+ """
1564
+ Export MVP data to a NetCDF file using xarray.
1568
1565
 
1569
- engine = 'netcdf4' if engine is None else engine
1570
- if engine == 'scipy' and compression:
1571
- print('Warning: scipy backend does not support compression; writing without compression.')
1572
- compression = False
1566
+ Args:
1567
+ filepath (str): Output NetCDF file path.
1568
+ corrected (bool): Also write corrected arrays if present (*_mvp_corr). Default False.
1569
+ compression (bool): Enable compression (engine dependent). Default True.
1570
+ engine (str|None): One of 'netcdf4', 'h5netcdf', 'scipy'. If None, choose netcdf4.
1571
+ per_profile_files (bool): If True, write one .nc per MVP cycle (two rows: down and up).
1572
+ """
1573
+ if not getattr(self, 'mvp', False):
1574
+ raise RuntimeError("No MVP data loaded. Call load_mvp_data() first.")
1573
1575
 
1574
- # Dimensions
1575
- n_prof, n_samp = self.PRES_mvp.shape
1576
+ engine = 'netcdf4' if engine is None else engine
1577
+ if engine == 'scipy' and compression:
1578
+ print('Warning: scipy backend does not support compression; writing without compression.')
1579
+ compression = False
1576
1580
 
1577
- # Coordinates
1578
- profile_idx = np.arange(n_prof, dtype=np.int32)
1579
- sample_idx = np.arange(n_samp, dtype=np.int32)
1581
+ # Dimensions
1582
+ n_prof, n_samp = self.PRES_mvp.shape
1580
1583
 
1581
- # Direction per profile (down/up)
1582
- direction = None
1583
- if hasattr(self, 'DIR') and len(self.DIR) == n_prof:
1584
- direction = np.array(self.DIR, dtype=object)
1585
- else:
1586
- # Fallback based on even/odd
1587
- direction = np.array(['down' if i % 2 == 0 else 'up' for i in range(n_prof)], dtype=object)
1588
-
1589
- # Per-sample time as seconds since reference origin
1590
- # TIME_mvp is in days relative to self.date_ref
1591
- time_seconds = None
1592
- if hasattr(self, 'TIME_mvp'):
1593
- time_seconds = self.TIME_mvp * 24.0 * 3600.0
1594
- else:
1595
- time_seconds = np.full((n_prof, n_samp), np.nan)
1596
-
1597
- # Per-profile datetime (one timestamp per cast pair); map using i//2
1598
- profile_time = None
1599
- if hasattr(self, 'DATETIME_mvp') and len(getattr(self, 'DATETIME_mvp', [])) > 0:
1600
- prof_times = []
1601
- for i in range(n_prof):
1602
- j = i // 2
1603
- if j < len(self.DATETIME_mvp) and self.DATETIME_mvp[j] is not None:
1604
- prof_times.append(np.datetime64(self.DATETIME_mvp[j]))
1605
- else:
1606
- prof_times.append(np.datetime64('NaT'))
1607
- profile_time = np.array(prof_times, dtype='datetime64[ns]')
1608
- else:
1609
- profile_time = np.array([np.datetime64('NaT')] * n_prof, dtype='datetime64[ns]')
1584
+ # Coordinates
1585
+ profile_idx = np.arange(n_prof, dtype=np.int32)
1586
+ sample_idx = np.arange(n_samp, dtype=np.int32)
1587
+
1588
+ # Direction per profile (down/up)
1589
+ direction = None
1590
+ if hasattr(self, 'DIR') and len(self.DIR) == n_prof:
1591
+ direction = np.array(self.DIR, dtype=object)
1592
+ else:
1593
+ # Fallback based on even/odd
1594
+ direction = np.array(['down' if i % 2 == 0 else 'up' for i in range(n_prof)], dtype=object)
1595
+
1596
+ # Per-sample time as seconds since reference origin
1597
+ # TIME_mvp is in days relative to self.date_ref
1598
+ time_seconds = None
1599
+ if hasattr(self, 'TIME_mvp'):
1600
+ time_seconds = self.TIME_mvp * 24.0 * 3600.0
1601
+ else:
1602
+ time_seconds = np.full((n_prof, n_samp), np.nan)
1603
+
1604
+ # Per-profile datetime (one timestamp per cast pair); map using i//2
1605
+ profile_time = None
1606
+ if hasattr(self, 'DATETIME_mvp') and len(getattr(self, 'DATETIME_mvp', [])) > 0:
1607
+ prof_times = []
1608
+ for i in range(n_prof):
1609
+ j = i // 2
1610
+ if j < len(self.DATETIME_mvp) and self.DATETIME_mvp[j] is not None:
1611
+ prof_times.append(np.datetime64(self.DATETIME_mvp[j]))
1612
+ else:
1613
+ prof_times.append(np.datetime64('NaT'))
1614
+ profile_time = np.array(prof_times, dtype='datetime64[ns]')
1615
+ else:
1616
+ profile_time = np.array([np.datetime64('NaT')] * n_prof, dtype='datetime64[ns]')
1610
1617
 
1611
- # Build dataset variables safely
1612
- data_vars = {}
1618
+ # Build dataset variables safely
1619
+ data_vars = {}
1613
1620
 
1614
- def add_var(var_name, arr, units=None, long_name=None):
1615
- if arr is None:
1616
- return
1617
- data_vars[var_name] = (
1618
- ('profile', 'sample'), arr,
1619
- {k: v for k, v in [('units', units), ('long_name', long_name)] if v is not None}
1620
- )
1621
-
1622
- add_var('PRES', getattr(self, 'PRES_mvp', None), units='dbar', long_name='Sea water pressure')
1623
- add_var('TEMP', getattr(self, 'TEMP_mvp', None), units='degC', long_name='In-situ temperature')
1624
- add_var('COND', getattr(self, 'COND_mvp', None), units='mS/cm', long_name='Conductivity')
1625
- add_var('SAL', getattr(self, 'SALT_mvp', None), units='psu', long_name='Practical salinity')
1626
- add_var('SOUNDVEL', getattr(self, 'SOUNDVEL_mvp', None), units='m s-1', long_name='Sound speed')
1627
- add_var('DO', getattr(self, 'DO_mvp', None), units='ml/L', long_name='Dissolved oxygen')
1628
- add_var('TEMP2', getattr(self, 'TEMP2_mvp', None), units='degC', long_name='Oxygen sensor temperature')
1629
- add_var('SUNA', getattr(self, 'SUNA_mvp', None), long_name='SUNA raw/derived')
1630
- add_var('FLUO', getattr(self, 'FLUO_mvp', None), units='ug/L', long_name='Chl fluorescence')
1631
- add_var('TURB', getattr(self, 'TURB_mvp', None), units='NTU', long_name='Turbidity')
1632
- add_var('PH', getattr(self, 'PH_mvp', None), units='1', long_name='pH')
1633
-
1634
- # Position and time arrays (2D)
1635
- if hasattr(self, 'LAT_mvp'):
1636
- add_var('LATITUDE', self.LAT_mvp, units='degrees_north', long_name='Latitude at sample')
1637
- if hasattr(self, 'LON_mvp'):
1638
- add_var('LONGITUDE', self.LON_mvp, units='degrees_east', long_name='Longitude at sample')
1639
- # Time seconds since reference
1640
- data_vars['TIME'] = (
1641
- ('profile', 'sample'), time_seconds,
1642
- {
1643
- 'units': f'seconds since {self.date_ref.strftime("%Y-%m-%d %H:%M:%S")}',
1644
- 'long_name': 'Time at sample'
1645
- }
1621
+ def add_var(var_name, arr, units=None, long_name=None):
1622
+ if arr is None:
1623
+ return
1624
+ data_vars[var_name] = (
1625
+ ('profile', 'sample'), arr,
1626
+ {k: v for k, v in [('units', units), ('long_name', long_name)] if v is not None}
1646
1627
  )
1647
-
1648
- # Include corrected arrays if requested and present
1649
- if corrected:
1650
- def add_corr(name, attr, units=None, long_name=None):
1651
- if hasattr(self, attr):
1652
- data_vars[name] = (
1653
- ('profile', 'sample'), getattr(self, attr),
1654
- {k: v for k, v in [('units', units), ('long_name', long_name)] if v is not None}
1655
- )
1656
- add_corr('pressure_corrected', 'PRES_mvp_corr', units='dbar', long_name='Corrected pressure')
1657
- add_corr('temperature_corrected', 'TEMP_mvp_corr', units='degC', long_name='Corrected temperature')
1658
- add_corr('conductivity_corrected', 'COND_mvp_corr', units='mS/cm', long_name='Corrected conductivity')
1659
- add_corr('salinity_corrected', 'SALT_mvp_corr', units='psu', long_name='Corrected salinity')
1660
- if hasattr(self, 'TIME_mvp_corr'):
1661
- data_vars['time_corrected'] = (
1662
- ('profile', 'sample'), self.TIME_mvp_corr * 24.0 * 3600.0,
1663
- {
1664
- 'units': f'seconds since {self.date_ref.strftime("%Y-%m-%d %H:%M:%S")}',
1665
- 'long_name': 'Corrected time at sample'
1666
- }
1667
- )
1668
- if hasattr(self, 'LAT_mvp_corr'):
1669
- add_corr('latitude_corrected', 'LAT_mvp_corr', units='degrees_north', long_name='Corrected latitude at sample')
1670
- if hasattr(self, 'LON_mvp_corr'):
1671
- add_corr('longitude_corrected', 'LON_mvp_corr', units='degrees_east', long_name='Corrected longitude at sample')
1672
-
1673
- # Coordinates and auxiliary per-profile variables
1674
- coords = {
1675
- 'profile': ('profile', profile_idx),
1676
- 'sample': ('sample', sample_idx)
1628
+
1629
+ add_var('PRES', getattr(self, 'PRES_mvp', None), units='dbar', long_name='Sea water pressure')
1630
+ add_var('TEMP', getattr(self, 'TEMP_mvp', None), units='degC', long_name='In-situ temperature')
1631
+ add_var('COND', getattr(self, 'COND_mvp', None), units='mS/cm', long_name='Conductivity')
1632
+ add_var('SAL', getattr(self, 'SALT_mvp', None), units='psu', long_name='Practical salinity')
1633
+ add_var('SOUNDVEL', getattr(self, 'SOUNDVEL_mvp', None), units='m s-1', long_name='Sound speed')
1634
+ add_var('DO', getattr(self, 'DO_mvp', None), units='ml/L', long_name='Dissolved oxygen')
1635
+ add_var('TEMP2', getattr(self, 'TEMP2_mvp', None), units='degC', long_name='Oxygen sensor temperature')
1636
+ add_var('SUNA', getattr(self, 'SUNA_mvp', None), long_name='SUNA raw/derived')
1637
+ add_var('FLUO', getattr(self, 'FLUO_mvp', None), units='ug/L', long_name='Chl fluorescence')
1638
+ add_var('TURB', getattr(self, 'TURB_mvp', None), units='NTU', long_name='Turbidity')
1639
+ add_var('PH', getattr(self, 'PH_mvp', None), units='1', long_name='pH')
1640
+
1641
+ # Position and time arrays (2D)
1642
+ if hasattr(self, 'LAT_mvp'):
1643
+ add_var('LATITUDE', self.Lat_mvp, units='degrees_north', long_name='Latitude at sample')
1644
+ if hasattr(self, 'LON_mvp'):
1645
+ add_var('LONGITUDE', self.Lon_mvp, units='degrees_east', long_name='Longitude at sample')
1646
+ # Time seconds since reference
1647
+ data_vars['TIME'] = (
1648
+ ('profile', 'sample'), time_seconds,
1649
+ {
1650
+ 'units': f'seconds since {self.date_ref.strftime("%Y-%m-%d %H:%M:%S")}',
1651
+ 'long_name': 'Time at sample'
1677
1652
  }
1653
+ )
1678
1654
 
1679
- # Encode direction/time according to engine capabilities
1680
- if engine in ('netcdf4', 'h5netcdf'):
1681
- coords['direction'] = ('profile', direction.astype('U'), {'long_name': 'Profile direction'})
1682
- coords['profile_time'] = ('profile', profile_time, {'long_name': 'Profile nominal time'})
1683
- else:
1684
- # scipy backend: avoid object strings and datetime; use numeric fallbacks
1685
- dir_flag = np.where(direction.astype('U') == 'down', 0, 1).astype('int8')
1686
- coords['direction_flag'] = (
1687
- 'profile', dir_flag, {'long_name': 'Profile direction (0=down,1=up)'}
1688
- )
1689
- ref = np.datetime64(self.date_ref)
1690
- pt = profile_time.astype('datetime64[s]')
1691
- mask = (pt == np.datetime64('NaT'))
1692
- secs = (pt - ref).astype('timedelta64[s]').astype('float64')
1693
- secs[mask] = np.nan
1694
- coords['profile_time_sec'] = (
1695
- 'profile', secs,
1696
- {'units': f'seconds since {self.date_ref.strftime("%Y-%m-%d %H:%M:%S")}',
1697
- 'long_name': 'Profile nominal time'}
1698
- )
1699
-
1700
- # Optional per-profile lat/lon (first valid sample)
1701
- def first_valid(vec):
1702
- # vec shape (n_prof, n_samp)
1703
- out = np.full((vec.shape[0],), np.nan)
1704
- for i in range(vec.shape[0]):
1705
- row = vec[i]
1706
- j = np.where(~np.isnan(row))[0]
1707
- if j.size:
1708
- out[i] = row[j[0]]
1709
- return out
1710
-
1711
- if hasattr(self, 'LAT_mvp'):
1712
- coords['profile_lat'] = (
1713
- 'profile', first_valid(self.LAT_mvp), {'units': 'degrees_north', 'long_name': 'Profile latitude'}
1714
- )
1715
- if hasattr(self, 'LON_mvp'):
1716
- coords['profile_lon'] = (
1717
- 'profile', first_valid(self.LON_mvp), {'units': 'degrees_east', 'long_name': 'Profile longitude'}
1655
+ # Include corrected arrays if requested and present
1656
+ if corrected:
1657
+ def add_corr(name, attr, units=None, long_name=None):
1658
+ if hasattr(self, attr):
1659
+ data_vars[name] = (
1660
+ ('profile', 'sample'), getattr(self, attr),
1661
+ {k: v for k, v in [('units', units), ('long_name', long_name)] if v is not None}
1662
+ )
1663
+ add_corr('pressure_corrected', 'PRES_mvp_corr', units='dbar', long_name='Corrected pressure')
1664
+ add_corr('temperature_corrected', 'TEMP_mvp_corr', units='degC', long_name='Corrected temperature')
1665
+ add_corr('conductivity_corrected', 'COND_mvp_corr', units='mS/cm', long_name='Corrected conductivity')
1666
+ add_corr('salinity_corrected', 'SALT_mvp_corr', units='psu', long_name='Corrected salinity')
1667
+ if hasattr(self, 'TIME_mvp_corr'):
1668
+ data_vars['time_corrected'] = (
1669
+ ('profile', 'sample'), self.TIME_mvp_corr * 24.0 * 3600.0,
1670
+ {
1671
+ 'units': f'seconds since {self.date_ref.strftime("%Y-%m-%d %H:%M:%S")}',
1672
+ 'long_name': 'Corrected time at sample'
1673
+ }
1718
1674
  )
1675
+ if hasattr(self, 'LAT_mvp_corr'):
1676
+ add_corr('latitude_corrected', 'LAT_mvp_corr', units='degrees_north', long_name='Corrected latitude at sample')
1677
+ if hasattr(self, 'LON_mvp_corr'):
1678
+ add_corr('longitude_corrected', 'LON_mvp_corr', units='degrees_east', long_name='Corrected longitude at sample')
1679
+
1680
+ # Coordinates and auxiliary per-profile variables
1681
+ coords = {
1682
+ 'profile': ('profile', profile_idx),
1683
+ 'sample': ('sample', sample_idx)
1684
+ }
1685
+
1686
+ # Encode direction/time according to engine capabilities
1687
+ if engine in ('netcdf4', 'h5netcdf'):
1688
+ coords['direction'] = ('profile', direction.astype('U'), {'long_name': 'Profile direction'})
1689
+ coords['profile_time'] = ('profile', profile_time, {'long_name': 'Profile nominal time'})
1690
+ else:
1691
+ # scipy backend: avoid object strings and datetime; use numeric fallbacks
1692
+ dir_flag = np.where(direction.astype('U') == 'down', 0, 1).astype('int8')
1693
+ coords['direction_flag'] = (
1694
+ 'profile', dir_flag, {'long_name': 'Profile direction (0=down,1=up)'}
1695
+ )
1696
+ ref = np.datetime64(self.date_ref)
1697
+ pt = profile_time.astype('datetime64[s]')
1698
+ mask = (pt == np.datetime64('NaT'))
1699
+ secs = (pt - ref).astype('timedelta64[s]').astype('float64')
1700
+ secs[mask] = np.nan
1701
+ coords['profile_time_sec'] = (
1702
+ 'profile', secs,
1703
+ {'units': f'seconds since {self.date_ref.strftime("%Y-%m-%d %H:%M:%S")}',
1704
+ 'long_name': 'Profile nominal time'}
1705
+ )
1719
1706
 
1720
- # Global attributes
1721
- attrs = {
1722
- 'title': 'MVP profile data',
1723
- 'Conventions': 'CF-1.8',
1724
- 'institution': 'LMD/CNRS',
1725
- 'source': 'PyMVP',
1726
- 'history': f"Created on {datetime.now().isoformat()}",
1727
- 'mvp_Yorig': int(self.Yorig)
1728
- }
1729
-
1730
- ds = xr.Dataset(data_vars=data_vars, coords=coords, attrs=attrs)
1731
-
1732
- # Compression encoding per engine
1733
- encoding = None
1734
- if compression:
1735
- if engine == 'netcdf4':
1736
- encoding = {name: {'zlib': True, 'complevel': 4} for name in data_vars.keys()}
1737
- elif engine == 'h5netcdf':
1738
- encoding = {name: {'compression': 'gzip', 'compression_opts': 4} for name in data_vars.keys()}
1739
-
1707
+ # Optional per-profile lat/lon (first valid sample)
1708
+ def first_valid(vec):
1709
+ # vec shape (n_prof, n_samp)
1710
+ out = np.full((vec.shape[0],), np.nan)
1711
+ for i in range(vec.shape[0]):
1712
+ row = vec[i]
1713
+ j = np.where(~np.isnan(row))[0]
1714
+ if j.size:
1715
+ out[i] = row[j[0]]
1716
+ return out
1717
+
1718
+ if hasattr(self, 'LAT_mvp'):
1719
+ coords['profile_lat'] = (
1720
+ 'profile', first_valid(self.Lat_mvp), {'units': 'degrees_north', 'long_name': 'Profile latitude'}
1721
+ )
1722
+ if hasattr(self, 'LON_mvp'):
1723
+ coords['profile_lon'] = (
1724
+ 'profile', first_valid(self.Lon_mvp), {'units': 'degrees_east', 'long_name': 'Profile longitude'}
1725
+ )
1740
1726
 
1741
- if (not per_profile_files) and filepath.lower().endswith('.nc'):
1742
- out_path = filepath
1743
- ds.to_netcdf(out_path, encoding=encoding, engine=engine)
1744
- print(f"NetCDF written: {out_path} using engine={engine}")
1745
- return
1746
- base_dir = filepath
1747
-
1748
- if not base_dir.endswith(os.sep):
1749
- base_dir = base_dir + os.sep
1750
-
1751
- base_name = "MVP_" + os.path.basename(self.data_path).rstrip(os.sep)
1752
- if per_profile_files:
1753
- # Write one file per pair (down/up)
1754
- total_pairs = (n_prof + 1) // 2
1755
- for i in range(total_pairs):
1756
- idxs = [k for k in (2*i, 2*i+1) if k < n_prof]
1757
- if not idxs:
1758
- continue
1759
- ds_i = ds.isel(profile=idxs)
1760
-
1761
- #add i to filename
1762
- fname = f"{base_name}_profile_{i:03d}.nc"
1763
- out_path = os.path.join(base_dir, fname)
1764
- ds_i.to_netcdf(out_path, encoding=encoding, engine=engine)
1765
- print(f"NetCDF written per profile into: {base_dir} using engine={engine}")
1766
- else:
1767
- file_name = f"{base_name}.nc"
1768
- out_path = os.path.join(base_dir, file_name)
1769
- ds.to_netcdf(out_path, encoding=encoding, engine=engine)
1770
- print(f"NetCDF written: {out_path} using engine={engine}")
1727
+ # Global attributes
1728
+ attrs = {
1729
+ 'title': 'MVP profile data',
1730
+ 'Conventions': 'CF-1.8',
1731
+ 'institution': 'LMD/CNRS',
1732
+ 'source': 'PyMVP',
1733
+ 'history': f"Created on {datetime.now().isoformat()}",
1734
+ 'mvp_Yorig': int(self.Yorig)
1735
+ }
1736
+
1737
+ ds = xr.Dataset(data_vars=data_vars, coords=coords, attrs=attrs)
1738
+
1739
+ # Compression encoding per engine
1740
+ encoding = None
1741
+ if compression:
1742
+ if engine == 'netcdf4':
1743
+ encoding = {name: {'zlib': True, 'complevel': 4} for name in data_vars.keys()}
1744
+ elif engine == 'h5netcdf':
1745
+ encoding = {name: {'compression': 'gzip', 'compression_opts': 4} for name in data_vars.keys()}
1746
+
1747
+
1748
+ if (not per_profile_files) and filepath.lower().endswith('.nc'):
1749
+ out_path = filepath
1750
+ ds.to_netcdf(out_path, encoding=encoding, engine=engine)
1751
+ print(f"NetCDF written: {out_path} using engine={engine}")
1752
+ return
1753
+ base_dir = filepath
1754
+
1755
+ if not base_dir.endswith(os.sep):
1756
+ base_dir = base_dir + os.sep
1757
+
1758
+ base_name = "MVP_" + os.path.basename(self.data_path).rstrip(os.sep)
1759
+ if per_profile_files:
1760
+ # Write one file per pair (down/up)
1761
+ total_pairs = (n_prof + 1) // 2
1762
+ for i in range(total_pairs):
1763
+ idxs = [k for k in (2*i, 2*i+1) if k < n_prof]
1764
+ if not idxs:
1765
+ continue
1766
+ ds_i = ds.isel(profile=idxs)
1767
+
1768
+ #add i to filename
1769
+ fname = f"{base_name}_profile_{i:03d}.nc"
1770
+ out_path = os.path.join(base_dir, fname)
1771
+ ds_i.to_netcdf(out_path, encoding=encoding, engine=engine)
1772
+ print(f"NetCDF written per profile into: {base_dir} using engine={engine}")
1773
+ else:
1774
+ file_name = f"{base_name}.nc"
1775
+ out_path = os.path.join(base_dir, file_name)
1776
+ ds.to_netcdf(out_path, encoding=encoding, engine=engine)
1777
+ print(f"NetCDF written: {out_path} using engine={engine}")
1771
1778
 
1772
1779
 
1773
- def help(self):
1774
- """
1775
- Print all methods of the class with their docstring (header).
1776
- """
1777
- for attr in dir(self):
1778
- if callable(getattr(self, attr)) and not attr.startswith("__"):
1779
- method = getattr(self, attr)
1780
- doc = method.__doc__
1781
- print(f"{attr}:\n{doc}\n{'-'*40}")
1780
+ def help(self):
1781
+ """
1782
+ Print all methods of the class with their docstring (header).
1783
+ """
1784
+ for attr in dir(self):
1785
+ if callable(getattr(self, attr)) and not attr.startswith("__"):
1786
+ method = getattr(self, attr)
1787
+ doc = method.__doc__
1788
+ print(f"{attr}:\n{doc}\n{'-'*40}")
1782
1789
 
1783
1790
 
1784
1791
  def plot_MVP_transect(self,var='TEMP',l_id=None,depth_max=None,depth_min=None,vmax=None,vmin=None,cmap=None):
@@ -1796,7 +1803,7 @@ class Analyzer:
1796
1803
 
1797
1804
  """
1798
1805
 
1799
- if self.hasattr('PRES_mvp_corr_interp') == False:
1806
+ if hasattr(self, 'PRES_mvp_corr_interp') == False:
1800
1807
  raise ValueError("Corrected and interpolated MVP data not available. Apply corrections and interpolation first.")
1801
1808
 
1802
1809
  if l_id is None:
@@ -1908,9 +1915,9 @@ class Analyzer:
1908
1915
  ax.invert_yaxis()
1909
1916
  ax.set_xlabel("Distance le long du transect [km]")
1910
1917
  ax.set_ylabel("Profondeur [m]")
1911
- ax.set_title("Transect de température (interpolé)")
1918
+ ax.set_title(f"{var} transect (interpolated)")
1912
1919
  cbar = plt.colorbar(pcm, ax=ax)
1913
- cbar.set_label("Température [°C]")
1920
+ cbar.set_label(f"{var} (units)")
1914
1921
  plt.show()
1915
1922
 
1916
1923
 
@@ -1074,3 +1074,55 @@ def align_profiles(P, T_ref, T_to_align_raw, min_depth=0,max_shift=20):
1074
1074
  T_out[T_out_indices[mask_corrected]] = T_corrected[mask_corrected]
1075
1075
 
1076
1076
  return T_out, deltaP, deltaT
1077
+
1078
+
1079
+
1080
+
1081
+ def find_nearest_profile(time_mvp,Lat_mvp,Lon_mvp,time_ctd,Lat_ctd,Lon_ctd,mode):
1082
+
1083
+ if mode=='Dist':
1084
+ idx = len(Lat_mvp)//2
1085
+ Lat_mvp = np.radians(Lat_mvp[idx])
1086
+ Lon_mvp = np.radians(Lon_mvp[idx])
1087
+
1088
+ R = 6371.0
1089
+
1090
+ min_dist = np.inf
1091
+ nearest_index = -1
1092
+
1093
+ for i in range(len(Lat_ctd)):
1094
+
1095
+ lat,lon = np.radians(Lat_ctd[i]), np.radians(Lon_ctd[i])
1096
+ mask = np.isfinite(lat) & np.isfinite(lon)
1097
+ lat,lon = lat[mask], lon[mask]
1098
+ lat,lon = lat[0],lon[0]
1099
+
1100
+ dlon = lon - Lon_mvp
1101
+ dlat = lat - Lat_mvp
1102
+ a = np.sin(dlat / 2)**2 + np.cos(Lat_mvp) * np.cos(lat) * np.sin(dlon / 2)**2
1103
+ c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))
1104
+ dist = R * c * 1e3 # Convert to meters
1105
+ if dist < min_dist:
1106
+ min_dist = dist
1107
+ nearest_index = i
1108
+
1109
+
1110
+ return nearest_index, min_dist
1111
+
1112
+ elif mode=='Time':
1113
+ time_mvp = time_mvp[len(time_mvp)//2] # Take the middle time of the MVP cycle as reference
1114
+
1115
+ min_time_diff = np.inf
1116
+ nearest_index = -1
1117
+ for i in range(len(time_ctd)):
1118
+ time_diff = np.abs(time_ctd[i,-1] - time_mvp)
1119
+ if time_diff < min_time_diff:
1120
+ min_time_diff = time_diff
1121
+ nearest_index = i
1122
+ return nearest_index, min_time_diff
1123
+
1124
+ else:
1125
+ raise ValueError("Mode should be 'Dist' or 'Time'")
1126
+
1127
+
1128
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyMVP
3
- Version: 0.2.3
3
+ Version: 0.2.5
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.2.3"
3
+ version = "0.2.5"
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